回答

收藏

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

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

我发现这个大约9 岁的流行病SO 问题,并决定仔细检查结果。& y; |: s0 x! V6 h0 J
所以,我有 AMD Ryzen 9 5950X、clang  10 和 Linux,我从问题中复制粘贴代码,这是我得到的:0 @' J1 [) G8 E  ]' Y
排序 - 0.549702s
8 Y! D& D) T4 k
    ~/d/so_sorting_faster$ cat main.cpp | grep "std::sort" && clang   -O3 main.cpp && ./a.out    std::sort(data,data   arraySize);0.549702sum = 314931600000
    " k& e  k1 t3 f$ i1 i( l
未分类 - 0.546554s, h& v/ H* I- o; A; {; q$ Z
    ~/d/so_sorting_faster $ cat main.cpp | grep "std::sort" && clang   -O3 main.cpp && ./a.out    // std::sort(data,data   arraySize);0.546554sum = 3149316000003 t; \2 x1 d( E: G1 \5 T
我很确定 unsorted 版本比 3ms 快的事实只是噪音,但似乎不再慢了。3 w: Q6 i1 H; F: l' k) n' Q4 k  [. r
那么,CPU 的架构发生了什么变化?(让它不再慢一个数量级)?
# o# j2 V' x( Q1 f4 h以下是多次操作的结果:- @5 }1 v, T, a- z) o; b$ c
    Unsorted: 0.543557 0.551147 0.541722 0.555599Sorted:   0.542587 0.559719 0.53938  0.557909
    9 |  f% V  L  }/ Q1 Z
以防万一,这是我的 main.cpp:
& e3 X. N1 c9 B, _2 @4 ~( x5 U

    ( m  y$ L3 P# p3 ]0 o" l  O
  • #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 更新3 D; ?1 e& f* Y* J5 c8 E( d6 N
  • (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
    4 }4 \: C2 X; q& J2 O* O
我认为这个问题仍然相关- 几乎没有区别。
6 x+ a1 w2 i2 d2 B, E- K                                                               
$ P  ^5 y" D% A; C  X5 Q    解决方案:                                                               
& H: l  }0 _/ \5 n. \9 z/ l- }                                                                您链接中的几个答案是将代码重写为无分支,以避免任何分支预测。这就是你更新的编译器所做的。
# X0 O- x0 \3 Q3 e  K: K5 K+ N具体来说,带-O3 矢量化内部循环clang   10 Godbolt 程序集上的代码是第 36-67 行。代码有点复杂,但你永远看不到的是data[c] >= 128测试中的任何条件分支。相反,它使用向量比较指令 ( pcmpgtd),输出是一个掩码, 1 表示匹配元素,0 表示不匹配。pand带有此掩码的后续元素将不匹配元素替换为 0,因此当它们无条件地添加到总和时,它们不会做出任何贡献。, ?8 |, u3 j2 d7 a- Y9 M
粗略的 C   等价物是
6 _. T2 J3 ~5 M* j6 F7 R9 F
    sum  = data[c] & -(data[c] >= 128);2 e% C  ~1 J& o
代码实际上sum为数组的偶数和奇数元素保留了两个 64 位,使其并行累积,然后在循环结束时加入。- X6 k8 Z  q; ~8 K& W
一些额外的复杂性是 32 位data元素符号扩展到 64 位置;这就是序列喜欢pxor xmm5,xmm5 ; pcmpgtd xmm5,xmm4 ; punpckldq xmm4,xmm完成的-mavx2.你会看到一个更简单的vpmovsxdq ymm5,xmm5的地方。9 X% V* r1 G7 ~0 ^; |% p0 L
由于循环已经展开,代码看起来也很长,data 8 元素每次迭代处理。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则