排序数组对程序运行的影响

来源:互联网 发布:新疆移动4g网络什么时候开通 编辑:程序博客网 时间:2024/05/16 11:27

问题:使用排序过的数组,比未排序的数组运行速度要快。作为有探究精神的你,肯定要问为什么?第一反应,它应该和存储有关,当排序后数组被连续的存储在一块连续的内存(应该是内存还是地址块更恰当呢?)中,如图:




那么最后造成速度快慢的原因,应该源自于两种存储方式下,对数据访问的不同所造成。

接下来,老规矩,上代码测试。

#include <algorithm>#include <ctime>#include <iostream>int main(){    // Generate data    const unsigned arraySize = 32768;    int data[arraySize];        for (unsigned c = 0; c < arraySize; ++c)        data[c] = std::rand() % 256;        // !!! With this, the next loop runs faster    std::sort(data, data + arraySize);        clock_t start = clock();    long long sum = 0;        for (unsigned i = 0; i < 100000; ++i)    {        // Primary loop        for (unsigned c = 0; c < arraySize; ++c)        {            if (data[c] >= 128)                sum += data[c];        }    }        double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;        std::cout << elapsedTime << std::endl;    std::cout << "sum = " << sum << std::endl;}

gcc编译后得到结果,排序后的情况:


未排序的情况:,时间上差了很多。当然了,这只能说明在数据量相当大的情况下,排序对性能有影响。再来测试一个数据量比较小的情况,将代码改为

#include <algorithm>#include <ctime>#include <iostream>int main(){    // Generate data    const unsigned arraySize = 32;    int data[arraySize];        for (unsigned c = 0; c < arraySize; ++c)        data[c] = std::rand() % 12;        // !!! With this, the next loop runs faster    std::sort(data, data + arraySize);        clock_t start = clock();    long long sum = 0;        for (unsigned i = 0; i < 100; ++i)    {        // Primary loop        for (unsigned c = 0; c < arraySize; ++c)        {            if (data[c] >= 6)                sum += data[c];        }    }        double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;        std::cout << elapsedTime << std::endl;    std::cout << "sum = " << sum << std::endl;}

排序--> 未排序-->

在数据量很小的情况下,两者并没有太大的差别。为了提高程序的性能,我们可以适当的使用sort()来优化。

回到主题,将test.cpp编译成汇编代码

LBB0_1:                                 ## => 对应操作  for (unsigned c = 0; c < arraySize; ++c)callq_rand                       ## =>               data[c] = std::rand() % 256;movl%eax, %ecxsarl$31, %ecxshrl$24, %ecxaddl%eax, %ecxandl$-256, %ecxsubl%ecx, %eaxmovl%eax, -131120(%rbp,%rbx,4)incq%rbxcmpq$32768, %rbxjneLBB0_1## BB#2:                                ## =>     std::sort(data, data + arraySize);leaq-48(%rbp), %rsi             ## =>     clock_t start = clock();leaq-131120(%rbp), %rdileaq-131136(%rbp), %rdxcallq__ZNSt3__16__sortIRNS_6__lessIiiEEPiEEvT0_S5_T_xorl%ebx, %ebxcallq_clockmovq%rax, %r14movdqaLCPI0_0(%rip), %xmm8movdqaLCPI0_1(%rip), %xmm9pxor%xmm8, %xmm9xorl%r13d, %r13d.align4, 0x90LBB0_3:                                 ## %overflow.checked                                        ## =>This Loop Header: Depth=1                                        ##     Child Loop BB0_4 Depth 2movd%r13, %xmm3pxor%xmm2, %xmm2xorl%eax, %eax.align4, 0x90LBB0_4:                                 ## %vector.body                                        ##   Parent Loop BB0_3 Depth=1                                        ## =>  内循环:for (unsigned c = 0; c < arraySize; ++c)                                        ##     {                                        ##          if (data[c] >= 128)                                        ##              sum += data[c];                                        ##     }movslq-131116(%rbp,%rax,4), %rcxmovd%rcx, %xmm5movslq-131120(%rbp,%rax,4), %rcxmovd%rcx, %xmm4punpcklqdq%xmm5, %xmm4movslq-131108(%rbp,%rax,4), %rcxmovd%rcx, %xmm6movslq-131112(%rbp,%rax,4), %rcxmovd%rcx, %xmm5punpcklqdq%xmm6, %xmm5movdqa%xmm4, %xmm6pxor%xmm8, %xmm6movdqa%xmm6, %xmm7pcmpgtd%xmm9, %xmm7pshufd$160, %xmm7, %xmm0pcmpeqd%xmm9, %xmm6pshufd$245, %xmm6, %xmm6pand%xmm0, %xmm6pshufd$245, %xmm7, %xmm0por%xmm6, %xmm0movdqa%xmm5, %xmm6pxor%xmm8, %xmm6movdqa%xmm6, %xmm7pcmpgtd%xmm9, %xmm7pshufd$160, %xmm7, %xmm1pcmpeqd%xmm9, %xmm6pshufd$245, %xmm6, %xmm6pand%xmm1, %xmm6pshufd$245, %xmm7, %xmm1por%xmm6, %xmm1paddq%xmm3, %xmm4paddq%xmm2, %xmm5pand%xmm0, %xmm4pandn%xmm3, %xmm0movdqa%xmm0, %xmm3por%xmm4, %xmm3pand%xmm1, %xmm5pandn%xmm2, %xmm1movdqa%xmm1, %xmm2por%xmm5, %xmm2addq$4, %raxcmpq$32768, %rax            ## imm = 0x8000jneLBB0_4## BB#5:                                ## %middle.block                                        ##   外部循环:for (unsigned i = 0; i < 100000; ++i)paddq%xmm3, %xmm2pshufd$78, %xmm2, %xmm0paddq%xmm2, %xmm0movd%xmm0, %r13incl%ebxcmpl$100000, %ebxjneLBB0_3## BB#6:                                static_cast<double>(clock() - start) / CLOCKS_PER_SEC;callq_clocksubq%r14, %raxmovd%rax, %xmm0punpckldqLCPI0_2(%rip), %xmm0 ## xmm0 = xmm0[0],mem[0],xmm0[1],mem[1]subpdLCPI0_3(%rip), %xmm0haddpd%xmm0, %xmm0divsdLCPI0_4(%rip), %xmm0movq__ZNSt3__14coutE@GOTPCREL(%rip), %rdicallq__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEdmovq%rax, %r14movq(%r14), %raxmovq-24(%rax), %rsiaddq%r14, %rsileaq-131136(%rbp), %r15movq%r15, %rdicallq__ZNKSt3__18ios_base6getlocEv

发现从汇编代码,并不能看出什么,两者完全一样。那么这个问题就不是汇编层级的优化导致的。然后在查找了一些资料后发现,是CPU流水指令有一个分支预测技术。

代码中内循环里有一个条件判断。每一次CPU执行这个条件判断时,CPU都可能跳转到循环开始处的指令,即不执行if后的指令。使用分支预测技术,当处理已经排序的数组时,在若干次data[c]>=128都不成立时(或第一次不成立时,取决于分支预测的实现),CPU预测这个分支是始终会跳转到循环开始的指令时,这个时候CPU将保持有效的执行,不需要重新等待到新的地址取指;同样,当data[c]>=128条件成立若干次后,CPU也可以预测这个分支是不必跳转的,那么这个时候CPU也可以保持高效执行。 
相反,如果是无序的数组,CPU的分支预测在很大程度上都无法预测成功,基本就是50%的预测成功概率,这将消耗大量的时间,因为CPU很多时间都会等待取指单元重新取指。

0 0