回答

收藏

为什么处理未排序数组与使用现代 x86-64 clang 处理排序数组的速度相同?

技术问答 技术问答 390 人阅读 | 0 人回复 | 2023-09-11

我发现这个大约9 岁的流行病SO 问题,并决定仔细检查结果。. }1 H- n5 Q. D4 W& }# t4 y
所以,我有 AMD Ryzen 9 5950X、clang  10 和 Linux,我从问题中复制粘贴代码,这是我得到的:' T- \6 g( R" P5 E
排序 - 0.549702s
# L2 [% D  n( r( Y* ^5 R
    ~/d/so_sorting_faster$ cat main.cpp | grep "std::sort" && clang   -O3 main.cpp && ./a.out    std::sort(data,data   arraySize);0.549702sum = 314931600000! j( T' B8 r- y  C! Q2 ~" z! w" W5 n
未分类 - 0.546554s) o( R# k! ?1 \, l) r$ `4 a9 Q
    ~/d/so_sorting_faster $ cat main.cpp | grep "std::sort" && clang   -O3 main.cpp && ./a.out    // std::sort(data,data   arraySize);0.546554sum = 314931600000
      R: k  i& [' [& D3 F$ _1 l) Z9 ~
我很确定 unsorted 版本比 3ms 快的事实只是噪音,但似乎不再慢了。7 H- w" c6 z, L( c$ t
那么,CPU 的架构发生了什么变化?(让它不再慢一个数量级)?
& l! ~8 @4 w# z1 W, L- U% j/ F( c以下是多次操作的结果:% P7 u: @4 `3 T* @& L, V
    Unsorted: 0.543557 0.551147 0.541722 0.555599Sorted:   0.542587 0.559719 0.53938  0.557909
    / }0 l4 W, Y' r) B# u
以防万一,这是我的 main.cpp:
5 S! |" h" W" S; U' u+ W% B+ ]
    # S9 S7 t3 c2 |) P8 Z
  • #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 更新+ C  t/ I0 {* E+ c1 U5 [) e+ C, f
  • (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
    : f+ {6 L5 `* {2 @6 X: Q
我认为这个问题仍然相关- 几乎没有区别。8 I2 i' |8 K+ C" \7 v: s
                                                               
& [- Z. o" d; h    解决方案:                                                                ( |$ _6 L! v$ W; m- J5 E
                                                                您链接中的几个答案是将代码重写为无分支,以避免任何分支预测。这就是你更新的编译器所做的。
+ l: P( y, z; c" D) O/ i具体来说,带-O3 矢量化内部循环clang   10 Godbolt 程序集上的代码是第 36-67 行。代码有点复杂,但你永远看不到的是data[c] >= 128测试中的任何条件分支。相反,它使用向量比较指令 ( pcmpgtd),输出是一个掩码, 1 表示匹配元素,0 表示不匹配。pand带有此掩码的后续元素将不匹配元素替换为 0,因此当它们无条件地添加到总和时,它们不会做出任何贡献。7 ?* j6 [6 Y/ i- W
粗略的 C   等价物是; d9 b/ h4 D/ w/ t
    sum  = data[c] & -(data[c] >= 128);
    ! _6 x' l& X2 N" ?% I  }  ]" [8 o+ E
代码实际上sum为数组的偶数和奇数元素保留了两个 64 位,使其并行累积,然后在循环结束时加入。* L% l# n( z6 c, S1 d' O* {" f, z
一些额外的复杂性是 32 位data元素符号扩展到 64 位置;这就是序列喜欢pxor xmm5,xmm5 ; pcmpgtd xmm5,xmm4 ; punpckldq xmm4,xmm完成的-mavx2.你会看到一个更简单的vpmovsxdq ymm5,xmm5的地方。* @3 ]/ ^& L4 ~! V' C# F$ M$ z
由于循环已经展开,代码看起来也很长,data 8 元素每次迭代处理。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则