无数据传输场景下OpenMP嵌套循环FLOPs/秒极低,如何提升浮点吞吐量?
优化方案及原因分析
性能低下的核心原因
- 编译参数未启用CPU支持的高级指令集:AMD Threadripper 1920X支持AVX2、FMA等256位宽浮点指令,单周期可执行的浮点操作数是默认SSE指令的4倍以上,但当前编译参数仅用了默认兼容指令集,没有利用这些硬件能力。
- 循环结构限制了向量化和指令级并行:单变量累加的循环存在数据依赖,编译器无法自动向量化,只能执行标量运算,同时也无法调度CPU的多个浮点执行单元并行工作。
- 运算类型吞吐上限低:当前只有单精度加法操作,单操作仅计1次FLOP,而FMA(乘加融合)操作一次可计2次FLOP,同等指令周期下吞吐量直接翻倍。
- 运算结果未被使用存在被优化风险:当前循环的i、j结果最终没有被输出或使用,极端情况下编译器可能直接删除整个循环逻辑,导致实际有效运算量不足。
具体优化步骤
1. 调整编译参数
在原有编译参数基础上新增如下选项:-march=native -ffast-math -fopenmp-simd
-march=native:让编译器自动识别当前CPU支持的所有指令集,启用AVX2、FMA等扩展-ffast-math:放松严格浮点语义限制,允许编译器做更多向量化优化-fopenmp-simd:启用OpenMP的SIMD向量化支持
2. 优化循环代码
修改循环结构,增加独立累加变量,适配向量化和指令级并行,同时避免运算被优化掉,优化后代码示例:
#include <chrono> #include <iostream> int main() { auto start_time = std::chrono::high_resolution_clock::now(); float total = 0.0f; #pragma omp parallel for reduction(+:total) for(int thread = 0; thread < 24; thread++) { // 8个独立累加变量,匹配AVX2的256位单精度宽度(8个float) float sum[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; for(int i = 0; i < 100000; i++) { #pragma omp simd for(int j = 0; j < 100000; j++) { // 用乘加操作触发FMA指令,单操作2 FLOP sum[j % 8] += 1.0f * 1.0f; } } // 合并结果,防止循环被优化 for(int k = 0; k < 8; k++) { total += sum[k]; } } auto end_time = std::chrono::high_resolution_clock::now(); auto time = end_time - start_time; std::cout << "运行耗时(ms): " << time / std::chrono::milliseconds(1) << std::endl; std::cout << "总累加结果: " << total << std::endl; return 0; }
3. 验证优化效果
优化后再次用perf统计,理论上整机吞吐量可接近300 GFLOPs以上,接近CPU标称的392 GFLOPs上限。
内容的提问来源于stack exchange,提问作者curiouscupcake
相关产品推荐
相关产品推荐

