在排序数组中,"=="比未排序数组快吗?

60

注意:所谓的重复问题与“<”和“>”比较有关,但不是“==”比较,因此不能回答我关于“==”运算符性能的问题。

很长一段时间以来,我一直认为“处理”排序数组应该比未排序数组快。起初,我认为在排序数组中使用“ == ”应该比在未排序数组中使用更快,因为-我猜测-分支预测的工作方式:

UNSORTEDARRAY:

5 == 100 F
43 == 100 F
100 == 100 T
250 == 100 F
6 == 100 F
(other elements to check)

SORTEDARRAY:

5 == 100 F
6 == 100 F
43 == 100 F
100 == 100 T
(no need to check other elements, so all are F)

我猜排序数组(SORTEDARRAY)应该比未排序数组(UNSORTEDARRAY)更快,但今天我使用代码在头文件中生成了2个数组进行测试,分支预测似乎没有像我想象的那样工作。

我生成了一个未排序的数组和一个已排序的数组进行测试:

srand(time(NULL));
int UNSORTEDARRAY[524288];
int SORTEDARRAY[sizeof(UNSORTEDARRAY)/sizeof(int)];
for(int i=0;i<sizeof(SORTEDARRAY)/sizeof(int);i++){
    SORTEDARRAY[i]=UNSORTEDARRAY[i]=rand();
}
sort(SORTEDARRAY,SORTEDARRAY+sizeof(SORTEDARRAY)/sizeof(int));
string u="const int UNSORTEDARRAY[]={";
string s="const int SORTEDARRAY[]={";
for(int i=0;i<sizeof(UNSORTEDARRAY)/sizeof(int);i++){
    u+=to_string(UNSORTEDARRAY[i])+",";
    s+=to_string(SORTEDARRAY[i])+",";
}
u.erase(u.end()-1);
s.erase(s.end()-1);
u+="};\n";
s+="};\n";
ofstream out("number.h");
string code=u+s;
out << code;
out.close();

所以进行测试时,只需像这样计算值是否等于RAND_MAX/2:
#include "number.h"
int main(){
int count;
    clock_t start = clock();
    for(int i=0;i<sizeof(SORTEDARRAY)/sizeof(int);i++){
        if(SORTEDARRAY[i]==RAND_MAX/2){
            count++;
        }
    }
    printf("%f\n",(float)(clock()-start)/CLOCKS_PER_SEC);
}

运行 3 次:

未排序数组

0.005376
0.005239
0.005220

SORTEDARRAY

0.005334
0.005120
0.005223

看起来只是一点点性能差异,所以我不相信它,然后尝试将“SORTEDARRAY [i] == RAND_MAX / 2”更改为“SORTEDARRAY [i]> RAND_MAX / 2”,以查看是否有所不同:

UNSORTEDARRAY

0.008407
0.008363
0.008606

SORTEDARRAY

0.005306
0.005227
0.005146

这一次有很大的不同。

在排序数组中,"==" 是否比未排序数组更快?如果是,为什么在排序数组中 ">" 操作比未排序数组更快但 "==" 却不是?


6
与 Stack Overflow 上最受欢迎的问题之一相关:https://dev59.com/iWgu5IYBdhLWcg3wkH6P - Andrzej Pronobis
我相信“处理”已排序的数组应该比未排序的数组更快:尝试自己回答为什么对于这个算法来说是真的。也就是说 - 你在每种情况下做了哪些工作以及做了多少。你可能会意识到答案是什么。 - viraptor
1
string 不是 C 语言的标准类型,使用 += 运算符将一个操作数设为 string 类型,另一个操作数设为 char * 类型是没有意义的。您确定这不是 C++ 代码吗? - autistic
我们甚至不知道你在使用什么编译器!也许你在完整的 mcve 中引入的偏差(我们也没有)取决于你使用的编译器和你正在为其编程的系统?这个问题太宽泛了,所以我投票关闭它。 - autistic
1
为什么你会假设“(不需要检查其他元素,所以所有元素都是F)”?编译器无法知道这一点,它只会盲目地检查每个内存位置。事实上,使用随机数据,它很少等于一个固定的值,因此非常容易被CPU预测。 - Mihai Timar
显示剩余2条评论
3个回答

35

一件立即浮现在脑海中的事情是CPU的分支预测算法。

在有序数组中,当进行>比较时,分支行为非常一致:首先,if条件始终为假,然后始终为真。这与即使是最简单的分支预测非常相符。

在未排序的数组中,>条件的结果基本上是随机的,因此会使任何分支预测落空。

这就是使排序版本更快的原因。

在进行==比较时,大多数情况下该条件为假,仅极少数情况下为真。无论数组是否已排序,这都很适用于分支预测。时间基本相同。


14

N.B. 我把这个作为答案是因为它太长了,放在评论里不太合适。

这里的效果与已经在 这个问题 的丰富回答中详细解释的内容完全相同。在那种情况下,处理排序数组更快,因为分支预测。

在这里,罪魁祸首再次是分支预测。 == 测试很少为真,所以分支预测对两者的影响大致相同。当您将其更改为 > 时,就会获得该问题中解释的行为,原因也是相同的。


道理:

我认为"处理"排序数组应该比[未]排序数组更快。

你需要知道为什么。这不是某些神奇的规则,也并不总是正确的。


7

== 运算符与排序关系比 > 运算符更少。 正确或不正确地预测 == 只取决于重复值的数量及其分布。

您可以使用 perf stat 查看性能计数器...

jason@io /tmp $ lz4 -d ints | perf stat ./proc-eq >/dev/null
Successfully decoded 104824717 bytes

 Performance counter stats for './proc-eq':

       5226.932577      task-clock (msec)         #    0.953 CPUs utilized
                31      context-switches          #    0.006 K/sec
                24      cpu-migrations            #    0.005 K/sec
             3,479      page-faults               #    0.666 K/sec
    15,763,486,767      cycles                    #    3.016 GHz
     4,238,973,549      stalled-cycles-frontend   #   26.89% frontend cycles idle
   <not supported>      stalled-cycles-backend
    31,522,072,416      instructions              #    2.00  insns per cycle
                                                  #    0.13  stalled cycles per insn
     8,515,545,178      branches                  # 1629.167 M/sec
        10,261,743      branch-misses             #    0.12% of all branches

       5.483071045 seconds time elapsed

jason@io /tmp $ lz4 -d ints | sort -n | perf stat ./proc-eq >/dev/null
Successfully decoded 104824717 bytes

 Performance counter stats for './proc-eq':

       5536.031410      task-clock (msec)         #    0.348 CPUs utilized
               198      context-switches          #    0.036 K/sec
                21      cpu-migrations            #    0.004 K/sec
             3,604      page-faults               #    0.651 K/sec
    16,870,541,124      cycles                    #    3.047 GHz
     5,300,218,855      stalled-cycles-frontend   #   31.42% frontend cycles idle
   <not supported>      stalled-cycles-backend
    31,526,006,118      instructions              #    1.87  insns per cycle
                                                  #    0.17  stalled cycles per insn
     8,516,336,829      branches                  # 1538.347 M/sec
        10,980,571      branch-misses             #    0.13% of all branches

jason@io /tmp $ lz4 -d ints | perf stat ./proc-gt >/dev/null
Successfully decoded 104824717 bytes

 Performance counter stats for './proc-gt':

       5293.065703      task-clock (msec)         #    0.957 CPUs utilized
                38      context-switches          #    0.007 K/sec
                50      cpu-migrations            #    0.009 K/sec
             3,466      page-faults               #    0.655 K/sec
    15,972,451,322      cycles                    #    3.018 GHz
     4,350,726,606      stalled-cycles-frontend   #   27.24% frontend cycles idle
   <not supported>      stalled-cycles-backend
    31,537,365,299      instructions              #    1.97  insns per cycle
                                                  #    0.14  stalled cycles per insn
     8,515,606,640      branches                  # 1608.823 M/sec
        15,241,198      branch-misses             #    0.18% of all branches

       5.532285374 seconds time elapsed

jason@io /tmp $ lz4 -d ints | sort -n | perf stat ./proc-gt >/dev/null

      15.930144154 seconds time elapsed

 Performance counter stats for './proc-gt':

       5203.873321      task-clock (msec)         #    0.339 CPUs utilized
                 7      context-switches          #    0.001 K/sec
                22      cpu-migrations            #    0.004 K/sec
             3,459      page-faults               #    0.665 K/sec
    15,830,273,846      cycles                    #    3.042 GHz
     4,456,369,958      stalled-cycles-frontend   #   28.15% frontend cycles idle
   <not supported>      stalled-cycles-backend
    31,540,409,224      instructions              #    1.99  insns per cycle
                                                  #    0.14  stalled cycles per insn
     8,516,186,042      branches                  # 1636.509 M/sec
        10,205,058      branch-misses             #    0.12% of all branches

      15.365528326 seconds time elapsed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接