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

据我所知,在大多数现代Intel CPU上需要三个周期进行SSE add和五个周期才能完成mul (参见例如Agner Fog的“指令表” )。 由于流水线操作,如果算法具有至少三个独立的求和,则每个周期可以获得一个add的吞吐量。 因为对于打包的addpd来说也是如此,并且标量addsd版本和SSE寄存器可以包含两个double的吞吐量,每个周期的吞吐量可以高达两个触发器。

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

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

我的尝试:

#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;
}double addmul(double add, double mul, int ops){// Need to initialise differently otherwise compiler might optimise awaydouble 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 loopdouble 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;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;}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();x = addmul(x, y, n);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

在英特尔酷睿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

使用打包版本( addpdmulpd )更改标量版本会使翻牌计数加倍,而不会改变执行时间,因此每个周期我只能获得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改变了独立浮点运算的顺序,目的是在可能的情况下交替添加addpdmulpd 。 同样适用于gcc-4.6.2 -O2 -march=core2

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

  • cl /O2 ,从64位编译器SDK为Windows 7做自动循环展开,似乎尝试和安排操作,使得三个addpd “有三个的替补mulpd的(当然,至少在我的系统和对于我的简单程序)。

  • 我的Core i5 750 ( Nehalem架构 )不喜欢交替添加和mul,并且似乎无法并行运行这两个操作。 但是,如果按3分组,它突然就像魔法一样。

  • 其他架构(可能是Sandy Bridge和其他架构)似乎能够并行执行add / mul而不会出现问题,如果它们在汇编代码中交替出现。

  • 虽然难以承认,但在我的系统中, cl /O2在我的系统的低级优化操作方面做得更好,并且实现了上述小C ++示例的接近峰值性能。 我在1.85-2.01翻牌/周期之间测量(在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<loops/3; i++) {mul1*=mul; mul2*=mul; mul3*=mul;sum1+=add; sum2+=add; sum3+=add;mul4*=mul; mul5*=mul; mul1*=mul;sum4+=add; sum5+=add; sum1+=add;mul2*=mul; mul3*=mul; mul4*=mul;sum2+=add; sum3+=add; sum4+=add;mul5*=mul; mul1*=mul; mul2*=mul;sum5+=add; sum1+=add; sum2+=add;mul3*=mul; mul4*=mul; mul5*=mul;sum3+=add; sum4+=add; sum5+=add;}

现在装配看起来像

.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
...

#1楼

分支绝对可以阻止您维持峰值理论性能。 如果您手动进行一些循环展开,您是否看到了差异? 例如,如果每次循环迭代放置5到10倍的ops:

for(int i=0; i<loops/5; i++) {mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;}

#2楼

在2.4GHz Intel Core 2 Duo上使用Intels icc Version 11.1

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

这非常接近理想的9.6 Gflops。

编辑:

哎呀,看看汇编代码似乎icc不仅可以对乘法进行矢量化,还可以将循环中的附加物拉出来。 强制更严格的fp语义,代码不再向量化:

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
Thread model: posix

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

        .align  4, 0x90
LBB2_4:                                 ## =>This Inner Loop Header: Depth=1addsd   %xmm2, %xmm3addsd   %xmm2, %xmm14addsd   %xmm2, %xmm5addsd   %xmm2, %xmm1addsd   %xmm2, %xmm4mulsd   %xmm2, %xmm0mulsd   %xmm2, %xmm6mulsd   %xmm2, %xmm7mulsd   %xmm2, %xmm11mulsd   %xmm2, %xmm13incl    %eaxcmpl    %r14d, %eaxjl      LBB2_4

EDIT3:

最后,有两点建议:首先,如果您喜欢这种类型的基准测试,请考虑使用rdtsc指令而不是gettimeofday(2) 。 它更加准确并且可以提供循环时间,这通常是您感兴趣的。 对于gcc和朋友,你可以这样定义:

#include <stdint.h>static __inline__ uint64_t rdtsc(void)
{uint64_t rval;__asm__ volatile ("rdtsc" : "=A" (rval));return rval;
}

其次,您应该多次运行基准程序并使用最佳性能 。 在现代操作系统中,许多事情并行发生,cpu可能处于低频省电模式等。重复运行程序会给你一个更接近理想情况的结果。


#3楼

我以前做过这个确切的任务。 但主要是测量功耗和CPU温度。 以下代码(相当长)在我的Core i7 2600K上实现了接近最佳状态。

这里需要注意的关键是大量的手动循环展开以及乘法和交错的交错......

完整的项目可以在我的GitHub上找到: https : //github.com/Mysticial/Flops

警告:

如果您决定编译并运行它,请注意您的CPU温度!
确保不要让它过热。 并确保CPU限制不会影响您的结果!

此外,对于运行此代码可能造成的任何损害,我不承担任何责任。

笔记:

  • 此代码针对x64进行了优化。 x86没有足够的寄存器来编译。
  • 此代码已经过测试,可以在Visual Studio 2010/2012和GCC 4.6上正常运行。
    ICC 11(英特尔编译器11)令人惊讶地难以编译它。
  • 这些适用于FMA之前的处理器。 为了在Intel Haswell和AMD Bulldozer处理器(及更高版本)上实现峰值FLOPS,将需要FMA(融合乘法加法)指令。 这些超出了本基准的范围。
#include <emmintrin.h>
#include <omp.h>
#include <iostream>
using namespace std;typedef unsigned long long uint64;double test_dp_mac_SSE(double x,double y,uint64 iterations){register __m128d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF;//  Generate starting data.r0 = _mm_set1_pd(x);r1 = _mm_set1_pd(y);r8 = _mm_set1_pd(-0.0);r2 = _mm_xor_pd(r0,r8);r3 = _mm_or_pd(r0,r8);r4 = _mm_andnot_pd(r8,r0);r5 = _mm_mul_pd(r1,_mm_set1_pd(0.37796447300922722721));r6 = _mm_mul_pd(r1,_mm_set1_pd(0.24253562503633297352));r7 = _mm_mul_pd(r1,_mm_set1_pd(4.1231056256176605498));r8 = _mm_add_pd(r0,_mm_set1_pd(0.37796447300922722721));r9 = _mm_add_pd(r1,_mm_set1_pd(0.24253562503633297352));rA = _mm_sub_pd(r0,_mm_set1_pd(4.1231056256176605498));rB = _mm_sub_pd(r1,_mm_set1_pd(4.1231056256176605498));rC = _mm_set1_pd(1.4142135623730950488);rD = _mm_set1_pd(1.7320508075688772935);rE = _mm_set1_pd(0.57735026918962576451);rF = _mm_set1_pd(0.70710678118654752440);uint64 iMASK = 0x800fffffffffffffull;__m128d MASK = _mm_set1_pd(*(double*)&iMASK);__m128d vONE = _mm_set1_pd(1.0);uint64 c = 0;while (c < iterations){size_t i = 0;while (i < 1000){//  Here's the meat - the part that really matters.r0 = _mm_mul_pd(r0,rC);r1 = _mm_add_pd(r1,rD);r2 = _mm_mul_pd(r2,rE);r3 = _mm_sub_pd(r3,rF);r4 = _mm_mul_pd(r4,rC);r5 = _mm_add_pd(r5,rD);r6 = _mm_mul_pd(r6,rE);r7 = _mm_sub_pd(r7,rF);r8 = _mm_mul_pd(r8,rC);r9 = _mm_add_pd(r9,rD);rA = _mm_mul_pd(rA,rE);rB = _mm_sub_pd(rB,rF);r0 = _mm_add_pd(r0,rF);r1 = _mm_mul_pd(r1,rE);r2 = _mm_sub_pd(r2,rD);r3 = _mm_mul_pd(r3,rC);r4 = _mm_add_pd(r4,rF);r5 = _mm_mul_pd(r5,rE);r6 = _mm_sub_pd(r6,rD);r7 = _mm_mul_pd(r7,rC);r8 = _mm_add_pd(r8,rF);r9 = _mm_mul_pd(r9,rE);rA = _mm_sub_pd(rA,rD);rB = _mm_mul_pd(rB,rC);r0 = _mm_mul_pd(r0,rC);r1 = _mm_add_pd(r1,rD);r2 = _mm_mul_pd(r2,rE);r3 = _mm_sub_pd(r3,rF);r4 = _mm_mul_pd(r4,rC);r5 = _mm_add_pd(r5,rD);r6 = _mm_mul_pd(r6,rE);r7 = _mm_sub_pd(r7,rF);r8 = _mm_mul_pd(r8,rC);r9 = _mm_add_pd(r9,rD);rA = _mm_mul_pd(rA,rE);rB = _mm_sub_pd(rB,rF);r0 = _mm_add_pd(r0,rF);r1 = _mm_mul_pd(r1,rE);r2 = _mm_sub_pd(r2,rD);r3 = _mm_mul_pd(r3,rC);r4 = _mm_add_pd(r4,rF);r5 = _mm_mul_pd(r5,rE);r6 = _mm_sub_pd(r6,rD);r7 = _mm_mul_pd(r7,rC);r8 = _mm_add_pd(r8,rF);r9 = _mm_mul_pd(r9,rE);rA = _mm_sub_pd(rA,rD);rB = _mm_mul_pd(rB,rC);i++;}//  Need to renormalize to prevent denormal/overflow.r0 = _mm_and_pd(r0,MASK);r1 = _mm_and_pd(r1,MASK);r2 = _mm_and_pd(r2,MASK);r3 = _mm_and_pd(r3,MASK);r4 = _mm_and_pd(r4,MASK);r5 = _mm_and_pd(r5,MASK);r6 = _mm_and_pd(r6,MASK);r7 = _mm_and_pd(r7,MASK);r8 = _mm_and_pd(r8,MASK);r9 = _mm_and_pd(r9,MASK);rA = _mm_and_pd(rA,MASK);rB = _mm_and_pd(rB,MASK);r0 = _mm_or_pd(r0,vONE);r1 = _mm_or_pd(r1,vONE);r2 = _mm_or_pd(r2,vONE);r3 = _mm_or_pd(r3,vONE);r4 = _mm_or_pd(r4,vONE);r5 = _mm_or_pd(r5,vONE);r6 = _mm_or_pd(r6,vONE);r7 = _mm_or_pd(r7,vONE);r8 = _mm_or_pd(r8,vONE);r9 = _mm_or_pd(r9,vONE);rA = _mm_or_pd(rA,vONE);rB = _mm_or_pd(rB,vONE);c++;}r0 = _mm_add_pd(r0,r1);r2 = _mm_add_pd(r2,r3);r4 = _mm_add_pd(r4,r5);r6 = _mm_add_pd(r6,r7);r8 = _mm_add_pd(r8,r9);rA = _mm_add_pd(rA,rB);r0 = _mm_add_pd(r0,r2);r4 = _mm_add_pd(r4,r6);r8 = _mm_add_pd(r8,rA);r0 = _mm_add_pd(r0,r4);r0 = _mm_add_pd(r0,r8);//  Prevent Dead Code Eliminationdouble out = 0;__m128d temp = r0;out += ((double*)&temp)[0];out += ((double*)&temp)[1];return out;
}void test_dp_mac_SSE(int tds,uint64 iterations){double *sum = (double*)malloc(tds * sizeof(double));double start = omp_get_wtime();#pragma omp parallel num_threads(tds){double ret = test_dp_mac_SSE(1.1,2.1,iterations);sum[omp_get_thread_num()] = ret;}double secs = omp_get_wtime() - start;uint64 ops = 48 * 1000 * iterations * tds * 2;cout << "Seconds = " << secs << endl;cout << "FP Ops  = " << ops << endl;cout << "FLOPs   = " << ops / secs << endl;double out = 0;int c = 0;while (c < tds){out += sum[c++];}cout << "sum = " << out << endl;cout << endl;free(sum);
}int main(){//  (threads, iterations)test_dp_mac_SSE(8,10000000);system("pause");
}

输出(1个线程,10000000次迭代) - 使用Visual Studio 2010 SP1编译 - x64版本:

Seconds = 55.5104
FP Ops  = 960000000000
FLOPs   = 1.7294e+010
sum = 2.22652

该机器是Core i7 2600K @ 4.4 GHz。 理论SSE峰值为4个触发器* 4.4 GHz = 17.6 GFlops 。 这段代码实现了17.3 GFlops - 不错。

输出(8个线程,10000000次迭代) - 使用Visual Studio 2010 SP1编译 - x64版本:

Seconds = 117.202
FP Ops  = 7680000000000
FLOPs   = 6.55279e+010
sum = 17.8122

理论SSE峰值为4个触发器* 4个核心* 4.4 GHz = 70.4个GFlops。 实际是65.5 GFlops


让我们更进一步。 AVX ...

#include <immintrin.h>
#include <omp.h>
#include <iostream>
using namespace std;typedef unsigned long long uint64;double test_dp_mac_AVX(double x,double y,uint64 iterations){register __m256d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF;//  Generate starting data.r0 = _mm256_set1_pd(x);r1 = _mm256_set1_pd(y);r8 = _mm256_set1_pd(-0.0);r2 = _mm256_xor_pd(r0,r8);r3 = _mm256_or_pd(r0,r8);r4 = _mm256_andnot_pd(r8,r0);r5 = _mm256_mul_pd(r1,_mm256_set1_pd(0.37796447300922722721));r6 = _mm256_mul_pd(r1,_mm256_set1_pd(0.24253562503633297352));r7 = _mm256_mul_pd(r1,_mm256_set1_pd(4.1231056256176605498));r8 = _mm256_add_pd(r0,_mm256_set1_pd(0.37796447300922722721));r9 = _mm256_add_pd(r1,_mm256_set1_pd(0.24253562503633297352));rA = _mm256_sub_pd(r0,_mm256_set1_pd(4.1231056256176605498));rB = _mm256_sub_pd(r1,_mm256_set1_pd(4.1231056256176605498));rC = _mm256_set1_pd(1.4142135623730950488);rD = _mm256_set1_pd(1.7320508075688772935);rE = _mm256_set1_pd(0.57735026918962576451);rF = _mm256_set1_pd(0.70710678118654752440);uint64 iMASK = 0x800fffffffffffffull;__m256d MASK = _mm256_set1_pd(*(double*)&iMASK);__m256d vONE = _mm256_set1_pd(1.0);uint64 c = 0;while (c < iterations){size_t i = 0;while (i < 1000){//  Here's the meat - the part that really matters.r0 = _mm256_mul_pd(r0,rC);r1 = _mm256_add_pd(r1,rD);r2 = _mm256_mul_pd(r2,rE);r3 = _mm256_sub_pd(r3,rF);r4 = _mm256_mul_pd(r4,rC);r5 = _mm256_add_pd(r5,rD);r6 = _mm256_mul_pd(r6,rE);r7 = _mm256_sub_pd(r7,rF);r8 = _mm256_mul_pd(r8,rC);r9 = _mm256_add_pd(r9,rD);rA = _mm256_mul_pd(rA,rE);rB = _mm256_sub_pd(rB,rF);r0 = _mm256_add_pd(r0,rF);r1 = _mm256_mul_pd(r1,rE);r2 = _mm256_sub_pd(r2,rD);r3 = _mm256_mul_pd(r3,rC);r4 = _mm256_add_pd(r4,rF);r5 = _mm256_mul_pd(r5,rE);r6 = _mm256_sub_pd(r6,rD);r7 = _mm256_mul_pd(r7,rC);r8 = _mm256_add_pd(r8,rF);r9 = _mm256_mul_pd(r9,rE);rA = _mm256_sub_pd(rA,rD);rB = _mm256_mul_pd(rB,rC);r0 = _mm256_mul_pd(r0,rC);r1 = _mm256_add_pd(r1,rD);r2 = _mm256_mul_pd(r2,rE);r3 = _mm256_sub_pd(r3,rF);r4 = _mm256_mul_pd(r4,rC);r5 = _mm256_add_pd(r5,rD);r6 = _mm256_mul_pd(r6,rE);r7 = _mm256_sub_pd(r7,rF);r8 = _mm256_mul_pd(r8,rC);r9 = _mm256_add_pd(r9,rD);rA = _mm256_mul_pd(rA,rE);rB = _mm256_sub_pd(rB,rF);r0 = _mm256_add_pd(r0,rF);r1 = _mm256_mul_pd(r1,rE);r2 = _mm256_sub_pd(r2,rD);r3 = _mm256_mul_pd(r3,rC);r4 = _mm256_add_pd(r4,rF);r5 = _mm256_mul_pd(r5,rE);r6 = _mm256_sub_pd(r6,rD);r7 = _mm256_mul_pd(r7,rC);r8 = _mm256_add_pd(r8,rF);r9 = _mm256_mul_pd(r9,rE);rA = _mm256_sub_pd(rA,rD);rB = _mm256_mul_pd(rB,rC);i++;}//  Need to renormalize to prevent denormal/overflow.r0 = _mm256_and_pd(r0,MASK);r1 = _mm256_and_pd(r1,MASK);r2 = _mm256_and_pd(r2,MASK);r3 = _mm256_and_pd(r3,MASK);r4 = _mm256_and_pd(r4,MASK);r5 = _mm256_and_pd(r5,MASK);r6 = _mm256_and_pd(r6,MASK);r7 = _mm256_and_pd(r7,MASK);r8 = _mm256_and_pd(r8,MASK);r9 = _mm256_and_pd(r9,MASK);rA = _mm256_and_pd(rA,MASK);rB = _mm256_and_pd(rB,MASK);r0 = _mm256_or_pd(r0,vONE);r1 = _mm256_or_pd(r1,vONE);r2 = _mm256_or_pd(r2,vONE);r3 = _mm256_or_pd(r3,vONE);r4 = _mm256_or_pd(r4,vONE);r5 = _mm256_or_pd(r5,vONE);r6 = _mm256_or_pd(r6,vONE);r7 = _mm256_or_pd(r7,vONE);r8 = _mm256_or_pd(r8,vONE);r9 = _mm256_or_pd(r9,vONE);rA = _mm256_or_pd(rA,vONE);rB = _mm256_or_pd(rB,vONE);c++;}r0 = _mm256_add_pd(r0,r1);r2 = _mm256_add_pd(r2,r3);r4 = _mm256_add_pd(r4,r5);r6 = _mm256_add_pd(r6,r7);r8 = _mm256_add_pd(r8,r9);rA = _mm256_add_pd(rA,rB);r0 = _mm256_add_pd(r0,r2);r4 = _mm256_add_pd(r4,r6);r8 = _mm256_add_pd(r8,rA);r0 = _mm256_add_pd(r0,r4);r0 = _mm256_add_pd(r0,r8);//  Prevent Dead Code Eliminationdouble out = 0;__m256d temp = r0;out += ((double*)&temp)[0];out += ((double*)&temp)[1];out += ((double*)&temp)[2];out += ((double*)&temp)[3];return out;
}void test_dp_mac_AVX(int tds,uint64 iterations){double *sum = (double*)malloc(tds * sizeof(double));double start = omp_get_wtime();#pragma omp parallel num_threads(tds){double ret = test_dp_mac_AVX(1.1,2.1,iterations);sum[omp_get_thread_num()] = ret;}double secs = omp_get_wtime() - start;uint64 ops = 48 * 1000 * iterations * tds * 4;cout << "Seconds = " << secs << endl;cout << "FP Ops  = " << ops << endl;cout << "FLOPs   = " << ops / secs << endl;double out = 0;int c = 0;while (c < tds){out += sum[c++];}cout << "sum = " << out << endl;cout << endl;free(sum);
}int main(){//  (threads, iterations)test_dp_mac_AVX(8,10000000);system("pause");
}

输出(1个线程,10000000次迭代) - 使用Visual Studio 2010 SP1编译 - x64版本:

Seconds = 57.4679
FP Ops  = 1920000000000
FLOPs   = 3.34099e+010
sum = 4.45305

理论AVX峰值为8个触发器* 4.4 GHz = 35.2个GFlops 。 实际是33.4 GFlops

输出(8个线程,10000000次迭代) - 使用Visual Studio 2010 SP1编译 - x64版本:

Seconds = 111.119
FP Ops  = 15360000000000
FLOPs   = 1.3823e+011
sum = 35.6244

理论AVX峰值为8个触发器* 4个核心* 4.4 GHz = 140.8 GFlops。 实际是138.2 GFlops


现在进行一些解释:

性能关键部分显然是内循环内的48条指令。 你会注意到它被分成4块,每块12条指令。 这12个指令块中的每一个都完全相互独立 - 平均需要6个周期才能执行。

因此,在使用问题之间有12条指令和6个循环。 乘法的延迟是5个周期,因此它足以避免延迟停顿。

需要规范化步骤以防止数据上溢/下溢。 这是必需的,因为无操作代码将缓慢地增加/减少数据的大小。

因此,如果你只使用全零并且摆脱标准化步骤,那么它实际上可能比这更好。 然而,由于我编写了测量功耗和温度的基准测试, 我必须确保触发器是“真实”数据,而不是零 - 因为执行单元可能很好地为零使用更少功率的特殊情况处理并产生较少的热量。


更多结果:

  • 英特尔酷睿i7 920 @ 3.5 GHz
  • Windows 7旗舰版x64
  • Visual Studio 2010 SP1 - x64发行版

主题:1

Seconds = 72.1116
FP Ops  = 960000000000
FLOPs   = 1.33127e+010
sum = 2.22652

理论SSE峰值:4个触发器* 3.5 GHz = 14.0 GFlops 。 实际是13.3 GFlops

主题:8

Seconds = 149.576
FP Ops  = 7680000000000
FLOPs   = 5.13452e+010
sum = 17.8122

理论SSE峰值:4个触发器* 4个核心* 3.5 GHz = 56.0 GFlops 。 实际是51.3 GFlops

在多线程运行中,我的处理器温度达到了76C! 如果运行这些,请确保结果不受CPU限制的影响。


  • 2 x Intel Xeon X5482 Harpertown @ 3.2 GHz
  • Ubuntu Linux 10 x64
  • GCC 4.5.2 x64 - (-O2 -msse3 -fopenmp)

主题:1

Seconds = 78.3357
FP Ops  = 960000000000
FLOPs   = 1.22549e+10
sum = 2.22652

理论SSE峰值:4个触发器* 3.2 GHz = 12.8个GFlops 。 实际是12.3 GFlops

主题:8

Seconds = 78.4733
FP Ops  = 7680000000000
FLOPs   = 9.78676e+10
sum = 17.8122

理论SSE峰值:4个触发器* 8个核心* 3.2 GHz = 102.4 GFlops 。 实际值是97.9 GFlops


#4楼

人们经常忘记英特尔架构中的一点,调度端口在Int和FP / SIMD之间共享。 这意味着在循环逻辑在浮点流中创建气泡之前,您将只获得一定数量的FP / SIMD突发。 神秘主义者从他的代码中获得了更多的失败,因为他在展开的循环中使用了更长的步幅。

如果你看看Nehalem / Sandy Bridge架构http://www.realworldtech.com/page.cfm?ArticleID=RWT091810191937&p=6,那很清楚会发生什么。

相比之下,由于INT和FP / SIMD管道具有独立的发布端口和自己的调度程序,因此在AMD(Bulldozer)上应该更容易达到峰值性能。

这只是理论上的,因为我没有这些处理器来测试。

如何实现每个周期4个FLOP的理论最大值?相关推荐

  1. 周期信号的傅里叶级数表示

    1. 线性时不变系统对复指数信号的响应 在研究 \(LTI\)(Linear and Time-invariant System)系统时,将信号表示成基本信号的线性组合是很有利的,但这些基本信号应该具 ...

  2. 大数据为何“赌”不对猪周期?

    撰文 / 张贺飞 编辑 / 沈洁 进入到2021年的财报季,猪企们却集体遭遇了寒冬. 在已经披露年度业绩预报的猪企中,亏损已然成了一种新常态.温氏.新希望.天邦.正邦等企业均交出了亏损的答卷,甚至有猪 ...

  3. 周期信号的傅里叶级数展开分析(利用MATLAB)

    实验三 周期信号的傅里叶级数展开分析 一.实验目的 1.掌握周期信号傅里叶级数分析的理论方法. 2.用MATLAB实现周期信号的傅里叶级数分解与综合. 3.用MATLAB实现周期信号的单边频谱及双边频 ...

  4. 创业投资——风险投资周期及股市趋势技术分析

    风险投资周期     保罗.A.冈珀斯 乔希.勒纳 经济科学出版社 2002-7 股市趋势技术分析 罗伯特.D.爱德华 约翰.迈吉 李诗林译 东方出版社 要写出点什么,真是有些为难.<风险投资周 ...

  5. 【数字语音处理】Part3 语音信号的短时时域分析:短时平均、短时自相关、语音端点检测、基音周期估值

    Part3 语音信号的短时时域分析 一.帧和加窗的概念 二.短时平均能量 三.短时平均幅度函数 四.短时平均过零率 五.短时自相关分析 六.基于能量和过零率的语音端点检测 七.基音周期估值 八.总结 ...

  6. 十大机器智能新型芯片:华为抢占一席,Google占比最多

    (图片付费下载自视觉中国) 整理 | 胡巍巍 来源 | CSDN(ID:CSDNnews) 当年,阿基米德爷爷说出"给我一个支点,我就能撬动地球"这句话时,估计没少遭受嘲讽. 然而 ...

  7. 机器智能芯片 10 大新秀!华为抢占一席,Google 占比最多!

    整理 | 胡巍巍 出品 | CSDN(ID:CSDNnews) 当年,阿基米德爷爷说出"给我一个支点,我就能撬动地球"这句话时,估计没少遭受嘲讽. 然而后来的我们,都曾在物理课本上 ...

  8. 电子工程术语和定义列表

    按字母顺序排列: 电子工程术语和定义列表,按字母顺序排列 1-Wire 单线(加地线)通信协议. 更多信息,请参考: 1-Wire存储器产品 1-Wire接口方案 1-Wire软件工具 1-Wire ...

  9. CPU与内存的那些事

    下面是网上看到的一些关于内存和CPU方面的一些很不错的文章. 整理如下: 转: CPU的等待有多久? 原文标题:What Your Computer Does While You Wait 原文地址: ...

最新文章

  1. Java 学习内容总结
  2. SpringBoot 项目中使用velocity模板(转载)
  3. linux中mysql回滚重演_DM7 达梦 数据库 数据守护(Data Watch) (1) -- 基本概念
  4. js input复选框选中父级同时子级也选中_突破百度JS反爬获取百度文库数据下载...
  5. Word2013中怎样设置同一文档内粘贴选项
  6. 兼容性好的overflow CSS清除浮动一例
  7. 共享主机与虚拟机文件方式一
  8. android ios开发难度对比,ios VS android:这不就是简洁与复杂最明显的对比
  9. 7、核心芯片说明文档
  10. pandas.melt()详解
  11. 全面梳理关系型数据库和NoSQL的使用情景
  12. AR Camera开发记录(二) -- 3D人脸模型
  13. 什么是CUDA与CUDNN
  14. 电化学工作站求峰高实现设计
  15. 笔记本电脑dns服务器没有响应怎么办,华硕笔记本重装系统后dns服务器未响应怎么办?...
  16. ucla研究生计算机科学,ucla研究生申请难度大不大?有哪些要求?
  17. 基于S7-200 PLC组态王组态和触摸屏的洗衣机控制系统的设计 熟悉工业电气控制系统的组成,熟悉PLC系统的设计
  18. 使用C4D制作iPhone模型:第1部分
  19. 重要信息通知(短信通知+语音播报)解决方案
  20. Linux提取nestat -anp 进程参数

热门文章

  1. ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]
  2. android IPC 进程间通讯
  3. Unable to execute dex: Multiple dex files define Lcom/myapp/R$array;
  4. 五分钟教会你函数式编程与闭包
  5. android 加载器loader详解
  6. python字典增加和删除_Python字典的基本用法实例分析【创建、增加、获取、修改、删除】...
  7. 在YII2框架中使用UEditor编辑器发布文章
  8. Hibernate一对多(注解)
  9. Yii框架2.0的视图和widgets表单的使用
  10. WIN7下回收站不小心删除的文件怎么恢复,免费数据恢复软件下载