姬長信(Redy)

c – 如何实现每个周期4个FLOP的理论最大值…


如何在现代x86-64 Intel CPU上实现每个周期4个浮点运算(双精度)的理论峰值性能?

据我所知,在大多数现代英特尔CPU上完成一个SSE添加和五个周期需要三个周期才能完成(参见例如Agner Fog’s ‘Instruction Tables’).由于流水线操作,如果算法具有至少三个独立的求和,则每个周期可以获得一个加法的吞吐量.因为对于打包的addpd来说也是如此,并且标量添加版本和SSE寄存器可以包含两个double,吞吐量可以高达每个周期两个触发器.

此外,似乎(虽然我没有看到任何适当的文档)add和mul可以并行执行,给出每个周期四个触发器的理论最大吞吐量.

但是,我无法用简单的C/C++程序复制该性能.我最好的尝试导致大约2.7个翻牌/周期.如果任何人都可以贡献一个简单的C/C++或汇编程序,它可以表现出非常高兴的峰值性能.

我的尝试:

#include 
#include 
#include 
#include 

double stoptime(void) {
   struct timeval t;
   gettimeofday(&t,NULL);
   return (double) t.tv_sec + t.tv_usec/1000000.0;
}

double addmul(double add, double mul, int ops){
   // Need to initialise differently otherwise compiler might optimise away
   double sum1=0.1, sum2=-0.1, sum3=0.2, sum4=-0.2, sum5=0.0;
   double mul1=1.0, mul2= 1.1, mul3=1.2, mul4= 1.3, mul5=1.4;
   int loops=ops/10;          // We have 10 floating point operations inside the loop
   double expected = 5.0*add*loops + (sum1+sum2+sum3+sum4+sum5)
               + pow(mul,loops)*(mul1+mul2+mul3+mul4+mul5);

   for (int i=0; i/n", argv[0]);
      printf("number of operations:  millions/n");
      exit(EXIT_FAILURE);
   }
   int n = atoi(argv[1]) * 1000000;
   if (n

编译

g++ -O2 -march=native addmul.cpp ; ./a.out 1000

在英特尔酷睿i5-750,2.66 GHz上产生以下输出.

addmul:  0.270 s, 3.707 Gflops, res=1.326463

也就是说,每个周期只有大约1.4个触发器.用汇编代码查看汇编代码
g -S -O2 -march = native -masm = intel addmul.cpp主循环看起来很像
最适合我:

.L4:
inc    eax
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
addsd    xmm13, xmm2
addsd    xmm12, xmm2
addsd    xmm11, xmm2
addsd    xmm10, xmm2
addsd    xmm9, xmm2
cmp    eax, ebx
jne    .L4

使用打包版本(addpd和mulpd)更改标量版本会使翻牌计数加倍,而不会改变执行时间,因此每个周期我只能获得2.8个翻牌.有一个简单的例子,每个周期实现四次触发吗?

Mysticial的精彩小程序;这是我的结果(虽然运行了几秒钟):

> gcc -O2 -march = nocona:10.66 Gflops中的5.6 Gflops(2.1 flops / cycle)
> cl / O2,openmp删除:10.1 Gflops,10.66 Gflops(3.8 flops / cycle)

这看起来有点复杂,但到目前为止我的结论:

> gcc -O2改变了独立浮点运算的顺序
交替的目的
addpd和mulpd如果可能的话.同样适用于gcc-4.6.2 -O2 -march = core2.
> gcc -O2 -march = nocona似乎保持了浮点运算的顺序
C源.
> cl / O2,来自64位编译器
SDK for Windows 7
自动循环展开,似乎尝试安排操作
所以三个addpd的组可以交替使用三个mulpd(好吧,至少在我的系统和我的简单程序中).
>我的Core i5 750(Nehalem architecture)
不喜欢交替添加和mul,似乎无法
并行运行这两个操作.但是,如果按3分组,它突然就像魔法一样.
>其他架构(可能是Sandy Bridge和其他)似乎
能够并行执行add / mul而不会出现问题
如果他们在汇编代码中交替.
>虽然很难承认,但在我的系统中,cl / O2在我的系统的低级优化操作方面做得更好,并且对于上面的小C示例实现了接近峰值的性能.我测量了
1.85-2.01 flops / cycle(在Windows中使用了clock()并不精确.我想,需要使用更好的计时器 – 感谢Mackie Messer).
>我用gcc管理的最好的是手动循环展开和安排
以三个为一组的加法和乘法.同
g -O2 -march = nocona addmul_unroll.cpp
我得到最好的0.207s,4.825 Gflops,相当于1.8个翻牌/周期
我现在很开心.

在C代码中,我用for替换了for循环

   for (int i=0; i

现在装配看起来像

.L4:
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
addsd    xmm13, xmm2
addsd    xmm12, xmm2
addsd    xmm11, xmm2
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
mulsd    xmm8, xmm3
addsd    xmm10, xmm2
addsd    xmm9, xmm2
addsd    xmm13, xmm2
...