# c++ - 我如何获得每个周期4个FLOP的理论最大值?

## optimization architecture (3)

``````for(int i=0; i<loops/5; i++) {
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
}
``````

``````#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>

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

// 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<loops; i++) {
mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
}
return  sum1+sum2+sum3+sum4+sum5+mul1+mul2+mul3+mul4+mul5 - expected;
}

int main(int argc, char** argv) {
if (argc != 2) {
printf("usage: %s <num>\n", argv[0]);
printf("number of operations: <num> millions\n");
exit(EXIT_FAILURE);
}
int n = atoi(argv[1]) * 1000000;
if (n<=0)
n=1000;

double x = M_PI;
double y = 1.0 + 1e-8;
double t = stoptime();
t = stoptime() - t;
printf("addmul:\t %.3f s, %.3f Gflops, res=%f\n", t, (double)n/t/1e9, x);
return EXIT_SUCCESS;
}
``````

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

``````addmul:  0.270 s, 3.707 Gflops, res=1.326463
``````

``````.L4:
inc    eax
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
cmp    eax, ebx
jne    .L4
``````

Mysticial的小程序; 这里是我的结果（尽管只运行了几秒）：

• `gcc -O2 -march=nocona` ：10.66 Gflops中的5.6 Gflops（2.1个触发器/周期）
• `cl /O2` ，openmp删除：10.66 Gflops中的10.1 Gflops（3.8个触发器/周期）

• `gcc -O2`更改独立浮点操作的顺序，以便在可能的情况下交替使用`addpd``mulpd` 。 同样适用于`gcc-4.6.2 -O2 -march=core2`

• `gcc -O2 -march=nocona`似乎保持了C ++源代码中定义的浮点操作顺序。

• `cl /O2` ，来自SDK for Windows 7的64位编译器会自动循环展开，并似乎尝试并安排操作，以便三个`addpd`组与三个`addpd`交替进行`mulpd` （至少在我的系统和为我的简单程序）。

• 我的Core i5 750Nahelem架构 ）不喜欢交替添加和mul，似乎无法并行运行这两种操作。 但是，如果将其分组为3，它就会像魔术一样突然运行。

• 尽管很难承认，但在我的系统上， `cl /O2`在我的系统的低级优化操作方面做得更好，并且达到了上述小C ++示例的接近最佳性能。 我测量的是1.85-2.01个触发器/周期（在Windows中使用的时钟（）并不是那么精确，我想，需要使用更好的定时器 - 感谢Mackie Messer）。

• 我用`gcc`管理的最好的方式是手动循环展开，并以三个一组的方式安排加法和乘法。 用`g++ -O2 -march=nocona addmul_unroll.cpp`可以得到`0.207s, 4.825 Gflops` ，这相当于1.8 flops / cycle，我现在很满意。

``````   for (int i=0; i<loops/3; i++) {
mul1*=mul; mul2*=mul; mul3*=mul;
mul4*=mul; mul5*=mul; mul1*=mul;

mul2*=mul; mul3*=mul; mul4*=mul;
mul5*=mul; mul1*=mul; mul2*=mul;

mul3*=mul; mul4*=mul; mul5*=mul;
}
``````

``````.L4:
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
mulsd    xmm8, xmm3
...
``````

``````Macintosh:~ mackie\$ icc -O3 -mssse3 -oaddmul addmul.cc && ./addmul 1000
addmul:  0.105 s, 9.525 Gflops, res=0.000000
Macintosh:~ mackie\$ icc -v
Version 11.1
``````

``````Macintosh:~ mackie\$ icc -O3 -mssse3 -oaddmul addmul.cc -fp-model precise && ./addmul 1000
addmul:  0.516 s, 1.938 Gflops, res=1.326463
``````

EDIT2：

``````Macintosh:~ mackie\$ clang -O3 -mssse3 -oaddmul addmul.cc && ./addmul 1000
addmul:  0.209 s, 4.786 Gflops, res=1.326463
Macintosh:~ mackie\$ clang -v
Apple clang version 3.0 (tags/Apple/clang-211.10.1) (based on LLVM 3.0svn)
Target: x86_64-apple-darwin11.2.0
``````

clang代码的内部循环如下所示：

``````        .align  4, 0x90
LBB2_4:                                 ## =>This Inner Loop Header: Depth=1
mulsd   %xmm2, %xmm0
mulsd   %xmm2, %xmm6
mulsd   %xmm2, %xmm7
mulsd   %xmm2, %xmm11
mulsd   %xmm2, %xmm13
incl    %eax
cmpl    %r14d, %eax
jl      LBB2_4
``````

EDIT3：

``````#include <stdint.h>

static __inline__ uint64_t rdtsc(void)
{
uint64_t rval;
__asm__ volatile ("rdtsc" : "=A" (rval));
return rval;
}
``````