回答

收藏

为什么处理未排序数组与使用现代 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/ W
    Unsorted: 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. g
    sum  = 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 元素每次迭代处理。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则