SSE:使用_mm_add_epi32没有看到加速

时间:2017-10-16 01:11:54

标签: c arrays x86 performance-testing sse

我希望SSE比不使用SSE更快。我是否需要添加一些额外的编译器标志?难道我没有看到加速,因为这是整数代码而不是浮点数?

调用/输出

$ sudo cp ~/Android/Sdk/platform-tools/adb /usr/bin

编译

$ make sum2
clang -O3 -msse -msse2 -msse3 -msse4.1 sum2.c ; ./a.out 123
n: 123
  SSE Time taken: 0 seconds 124 milliseconds
vector+vector:begin int: 1 5 127 0
vector+vector:end int: 0 64 66 68
NOSSE Time taken: 0 seconds 115 milliseconds
vector+vector:begin int: 1 5 127 0
vector+vector:end int: 0 64 66 68

sum2.c

$ clang --version
Apple LLVM version 9.0.0 (clang-900.0.37)
Target: x86_64-apple-darwin16.7.0
Thread model: posix

1 个答案:

答案 0 :(得分:4)

查看asm:clang -O2-O3可能会自动向量化add_iv_nosse(检查重叠,因为您没有使用int * restrict a和等等。)

使用-fno-tree-vectorize禁用自动矢量化,而不会阻止您使用内在函数。我建议clang -march=native -mno-avx -O3 -fno-tree-vectorize来测试我认为你想要测试的内容,标量整数与遗留SSE paddd。 (它适用于gcc和clang。在clang中,AFAIK它是特定于clang的-fno-vectorize的同义词。)

顺便说一下,同一个可执行文件中的计时会伤害第一个计时器,因为CPU不会立即转为全涡轮增压。在CPU达到全速之前,您可能已进入代码的定时部分。 (所以用for i in {1..10}; do time ./a.out; done背靠背运行几次。

在Linux上,我使用perf stat -r5 ./a.out使用性能计数器运行它5次(我将它拆开,以便一次运行测试其中一个,所以我可以查看性能计数器整个运行。)

代码审核:

您忘记了stdint.h uint32_t。我必须添加它才能到达compile on Godbolt to see the asm。 (假设clang-5.0类似于你正在使用的Apple clang版本。如果Apple的clang意味着默认的-mtune=选项,那么IDK​​就是IDK,但这是有意义的,因为它只是定位Mac。对于x86-64 OS X上的64位,基线SSSE3也是有意义的。)

您在debug_print上不需要noinline。另外,我建议CYCLE_COUNT使用其他名称。在这种情况下的周期让我想到时钟周期,所以称之为REP_COUNTREPEATS或其他。

将数组放在main的堆栈上可能没问题。你初始化两个输入数组(大多数为零,但添加性能不依赖于数据)。

这很好,因为保留它们未初始化可能意味着每个阵列的多个4k页面都是写入时复制映射到相同的物理零页面,因此您获得的数据超过预期的L1D缓存命中数量

SSE2循环应该是L2 / L3缓存带宽的瓶颈,因为工作设置为4 * 32kiB * 3 = 384 kiB,因此它大约是Intel CPU中256kiB L2缓存的1.5倍。

clang可能会比你的手动内在函数循环更多地展开它的自动矢量化循环。这可能解释了更好的性能,因为如果你没有获得2个负载+每个时钟1个存储,那么只有16B矢量(不是32B AVX2)可能不会使高速缓存带宽饱和。

更新:实际上循环开销非常极端,有3个指针增量+一个循环计数器,只能展开2来分摊它。

自动矢量化循环:

.LBB2_12:                               # =>This Inner Loop Header: Depth=1
    movdqu  xmm0, xmmword ptr [r9 - 16]
    movdqu  xmm1, xmmword ptr [r9]         # hoisted load for 2nd unrolled iter
    movdqu  xmm2, xmmword ptr [r10 - 16]
    paddd   xmm2, xmm0
    movdqu  xmm0, xmmword ptr [r10]
    paddd   xmm0, xmm1
    movdqu  xmmword ptr [r11 - 16], xmm2
    movdqu  xmmword ptr [r11], xmm0
    add     r9, 32
    add     r10, 32
    add     r11, 32
    add     rbx, -8               # add / jne  macro-fused on SnB-family CPUs
    jne     .LBB2_12

所以它有12个融合域uops,每3个时钟最多可以运行2个向量,每个时钟的前端发布带宽为4 uop。

它没有使用对齐的加载,因为编译器没有内联到main已知对齐的情况下没有该信息,并且您没有保证与{{1}对齐或者独立功能中的任何东西。对齐的加载(或AVX)将允许p = __builtin_assume_aligned(p, 16)使用内存操作数而不是单独的paddd加载。

手动矢量化循环使用对齐的加载来保存前端uop,但循环计数器的循环开销更多。

movdqu

所以它有11个融合域uops。它应该比自动矢量化循环运行得更快。你的计时方法可能会导致问题。

(除非混合加载和存储实际上使其不太理想。自动向量化循环执行4次加载然后2次存储。实际上这可以解释它。您的数组是4kiB的倍数,并且可能都具有相同的相对因此,你可能会在这里获得4k别名,这意味着CPU不确定商店是否与负载重叠。我认为有一个性能计数器你可以检查它。)< / p>

另请参阅Agner Fog's microarch guide (and instruction tables + optimization guide以及标记wiki中的其他链接,尤其是英特尔的优化指南。

代码wiki中还有一些优秀的SSE / SIMD初学者内容。