为什么处理未排序数组与使用现代 x86-64 clang 处理排序数组的速度相同?
技术问答
391 人阅读
|
0 人回复
|
2023-09-11
|
我发现这个大约9 岁的流行病SO 问题,并决定仔细检查结果。: k9 m6 ~" z' \1 I" T; O
所以,我有 AMD Ryzen 9 5950X、clang 10 和 Linux,我从问题中复制粘贴代码,这是我得到的:5 V" {6 q' v# T3 A: s8 j8 g/ _1 D
排序 - 0.549702s:
; L$ r+ r0 a) B~/d/so_sorting_faster$ cat main.cpp | grep "std::sort" && clang -O3 main.cpp && ./a.out std::sort(data,data arraySize);0.549702sum = 314931600000, e. W# b9 w$ w0 i( f
未分类 - 0.546554s:* k) w9 S, N/ h
~/d/so_sorting_faster $ cat main.cpp | grep "std::sort" && clang -O3 main.cpp && ./a.out // std::sort(data,data arraySize);0.546554sum = 314931600000. f$ D y* ~# K1 r; }' V% Y
我很确定 unsorted 版本比 3ms 快的事实只是噪音,但似乎不再慢了。
& [: s1 p4 o, O# o; L n& a8 j那么,CPU 的架构发生了什么变化?(让它不再慢一个数量级)?4 k9 t8 _3 x( x$ i/ h9 M
以下是多次操作的结果:
% B' r) ?' X5 O( G( |1 a/ WUnsorted: 0.543557 0.551147 0.541722 0.555599Sorted: 0.542587 0.559719 0.53938 0.557909
$ d+ Q( I# ]/ b 以防万一,这是我的 main.cpp:
* i: b9 |. H( b9 t! t, J: d" X& Z* [$ M8 B
- #include #include #include int main()()()()()(///)()()()()()(()()()()()())()()(////)())()()()())()()()///////)()()()())())()())()())())()()()()()()()())()()()()()()())()()()())()()()()()()())()()())()()()())()()()()()()()()()()()()//////)/)/)/)()()()()()()()()()()())()())())())()())()())()())()())()())()()())()())()()()()()())()))()))()))()())())())())()()()())()))()))())())()))()()))()()()))()()()))())())()))())()()())())()))())))))())))()()))())))())))()))()())()())()))()))()()()()()))())))))())())()()()()()))))()))())))))()))))()))())()()))))())()()()()()()()())()))()////////)))))))))))))()))))))))()))))))()()()()()()()())))Generate data const unsigned arraySize = int data[arraySize]; for (unsigned c = 0; c = sum = data[c]; double elapsedTime = static_cast(clock() - start) / CLOCKS_PER_SEC; std::cout 更新" F& A/ U& l8 }8 h T" y; t9 H' p' D
- (627680)元素较多:[code]Unsortedcat main.cpp | grep "std::sort" && clang -O3 main.cpp && ./a.out // std::sort(data,data arraySize);10.3814Sorted:cat main.cpp | grep "std::sort" && clang -O3 main.cpp && ./a.out std::sort(data,data arraySize);10.6885) D. H: D( }0 G
我认为这个问题仍然相关- 几乎没有区别。9 Y- R0 b+ q5 S( o3 _
5 j* |9 S' C% c% Y6 ^: e2 p7 d9 D, O; d 解决方案: ' o1 e! p% P3 O
您链接中的几个答案是将代码重写为无分支,以避免任何分支预测。这就是你更新的编译器所做的。1 b+ C8 e' u3 z% z/ V
具体来说,带-O3 矢量化内部循环clang 10 Godbolt 程序集上的代码是第 36-67 行。代码有点复杂,但你永远看不到的是data[c] >= 128测试中的任何条件分支。相反,它使用向量比较指令 ( pcmpgtd),输出是一个掩码, 1 表示匹配元素,0 表示不匹配。pand带有此掩码的后续元素将不匹配元素替换为 0,因此当它们无条件地添加到总和时,它们不会做出任何贡献。5 x7 E3 d" Y$ }+ F
粗略的 C 等价物是
0 S% S8 V" `( f& i/ y/ b3 c# w. gsum = data[c] & -(data[c] >= 128);+ z, u! K$ u& t$ J& ]! Z0 F
代码实际上sum为数组的偶数和奇数元素保留了两个 64 位,使其并行累积,然后在循环结束时加入。
; p. h2 U2 k& U' Y) \1 @- \9 _一些额外的复杂性是 32 位data元素符号扩展到 64 位置;这就是序列喜欢pxor xmm5,xmm5 ; pcmpgtd xmm5,xmm4 ; punpckldq xmm4,xmm完成的-mavx2.你会看到一个更简单的vpmovsxdq ymm5,xmm5的地方。
) R1 z g) v- U4 S- q$ V/ ~' b. ]由于循环已经展开,代码看起来也很长,data 8 元素每次迭代处理。 |
|
|
|
|
|