首页 > 解决方案 > 非常低的 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/秒的能力(我从屋顶线建模软件得到这个数字)。

我的问题是,我怎样才能获得更高的吞吐量?

标签: c++performanceoptimizationflops

解决方案


使用带有这些选项的 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 个)独立累加器手动展开循环

推荐阅读