我希望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
答案 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
的同义词。)
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_COUNT
或REPEATS
或其他。
将数组放在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以及x86标记wiki中的其他链接,尤其是英特尔的优化指南。
sse代码wiki中还有一些优秀的SSE / SIMD初学者内容。