c++ - 非常低的 FLOPs/秒,无需任何数据传输
问题描述
我在我的机器上测试了以下代码,看看我可以获得多少吞吐量。除了为每个线程分配两个嵌套循环之外,代码并没有做太多事情,
#include <chrono>
#include <iostream>
int main() {
auto start_time = std::chrono::high_resolution_clock::now();
#pragma omp parallel for
for(int thread = 0; thread < 24; thread++) {
float i = 0.0f;
while(i < 100000.0f) {
float j = 0.0f;
while (j < 100000.0f) {
j = j + 1.0f;
}
i = i + 1.0f;
}
}
auto end_time = std::chrono::high_resolution_clock::now();
auto time = end_time - start_time;
std::cout << time / std::chrono::milliseconds(1) << std::endl;
return 0;
}
令我惊讶的是,根据perf
$ perf stat -e all_dc_accesses -e fp_ret_sse_avx_ops.all cmake-build-release/roofline_prediction
8907
Performance counter stats for 'cmake-build-release/roofline_prediction':
325.372.690 all_dc_accesses
240.002.400.000 fp_ret_sse_avx_ops.all
8,909514307 seconds time elapsed
202,819795000 seconds user
0,059613000 seconds sys
在 8.83 秒内 240.002.400.000 FLOPs,机器仅达到 27.1 GFLOPs/秒,远低于 CPU 392 GFLOPs/秒的能力(我从屋顶线建模软件得到这个数字)。
我的问题是,我怎样才能获得更高的吞吐量?
- 编译器:GCC 9.3.0
- CPU:AMD Threadripper 1920X
- 优化级别:-O3
- OpenMP 的标志:-fopenmp
解决方案
使用带有这些选项的 GCC 9.3 编译,内部循环如下所示:
.L3:
addss xmm0, xmm2
comiss xmm1, xmm0
ja .L3
GCC 版本/选项的其他一些组合可能会导致循环被忽略,毕竟它并没有真正做任何事情(除了浪费时间)。
形成一个循环携带的addss
依赖关系,其中只有它自己。不过这并不快,在 Zen 1 上,每次迭代需要 3 个周期,所以每个周期的加法数是 1/3。每个周期的浮点加法的最大数量可以通过至少有 6 条独立addps
指令来实现(256vaddps
位可能会有所帮助,但 Zen 1 在内部执行这样的 256 位 SIMD 指令和 2 128 位操作),以处理 3 的延迟和每个周期的吞吐量为 2(因此任何时候都需要激活 6 个操作)。这相当于每个周期 8 次加法,是当前代码的 24 倍。
在 C++ 程序中,可以通过以下方式诱使编译器生成合适的机器代码:
- 使用
-ffast-math
(如果可能,并非总是如此) - 使用显式矢量化
_mm_add_ps
- 使用(至少 6 个)独立累加器手动展开循环
推荐阅读
- python - 我如何使用复杂网络理论在 SVM 中进行参数优化
- android - Jacoco java.lang.instrument.IllegalClassFormatException:检测类时出错
- java - 在没有实体的文件中休眠本机查询
- php - 如何使用 pdo 和 Ajax 更新 PHOTO
- c# - 如何检查用户输入的字符串是否代表浮点值?
- flutter - 颤振警报不出现
- css - 未在 Rails 应用程序中应用 CSS 更改
- javascript - JestJS - 多个 websocket 连接挂起 Jest
- python - 将包含小数的字符串转换为包含大熊猫列的 int 的字符串的干净方法?
- flutter - Controller.to 和 Controller.getInstance() 之间的 GetX 区别?