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

本文探讨了排序数组与未排序数组在程序运行速度上的差异,指出在大量数据时,排序数组能提升性能,原因涉及CPU的分支预测技术。通过代码测试和汇编分析,揭示了分支预测在处理有序数据时的效率优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

 

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

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

#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, %ecx
	sarl	$31, %ecx
	shrl	$24, %ecx
	addl	%eax, %ecx
	andl	$-256, %ecx
	subl	%ecx, %eax
	movl	%eax, -131120(%rbp,%rbx,4)
	incq	%rbx
	cmpq	$32768, %rbx
	jne	LBB0_1
## BB#2:                                ## =>     std::sort(data, data + arraySize);
	leaq	-48(%rbp), %rsi             ## =>     clock_t start = clock();
	leaq	-131120(%rbp), %rdi
	leaq	-131136(%rbp), %rdx
	callq	__ZNSt3__16__sortIRNS_6__lessIiiEEPiEEvT0_S5_T_
	xorl	%ebx, %ebx
	callq	_clock
	movq	%rax, %r14
	movdqa	LCPI0_0(%rip), %xmm8
	movdqa	LCPI0_1(%rip), %xmm9
	pxor	%xmm8, %xmm9
	xorl	%r13d, %r13d
	.align	4, 0x90
LBB0_3:                                 ## %overflow.checked
                                        ## =>This Loop Header: Depth=1
                                        ##     Child Loop BB0_4 Depth 2
	movd	%r13, %xmm3
	pxor	%xmm2, %xmm2
	xorl	%eax, %eax
	.align	4, 0x90
LBB0_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), %rcx
	movd	%rcx, %xmm5
	movslq	-131120(%rbp,%rax,4), %rcx
	movd	%rcx, %xmm4
	punpcklqdq	%xmm5, %xmm4
	movslq	-131108(%rbp,%rax,4), %rcx
	movd	%rcx, %xmm6
	movslq	-131112(%rbp,%rax,4), %rcx
	movd	%rcx, %xmm5
	punpcklqdq	%xmm6, %xmm5
	movdqa	%xmm4, %xmm6
	pxor	%xmm8, %xmm6
	movdqa	%xmm6, %xmm7
	pcmpgtd	%xmm9, %xmm7
	pshufd	$160, %xmm7, %xmm0
	pcmpeqd	%xmm9, %xmm6
	pshufd	$245, %xmm6, %xmm6
	pand	%xmm0, %xmm6
	pshufd	$245, %xmm7, %xmm0
	por	%xmm6, %xmm0
	movdqa	%xmm5, %xmm6
	pxor	%xmm8, %xmm6
	movdqa	%xmm6, %xmm7
	pcmpgtd	%xmm9, %xmm7
	pshufd	$160, %xmm7, %xmm1
	pcmpeqd	%xmm9, %xmm6
	pshufd	$245, %xmm6, %xmm6
	pand	%xmm1, %xmm6
	pshufd	$245, %xmm7, %xmm1
	por	%xmm6, %xmm1
	paddq	%xmm3, %xmm4
	paddq	%xmm2, %xmm5
	pand	%xmm0, %xmm4
	pandn	%xmm3, %xmm0
	movdqa	%xmm0, %xmm3
	por	%xmm4, %xmm3
	pand	%xmm1, %xmm5
	pandn	%xmm2, %xmm1
	movdqa	%xmm1, %xmm2
	por	%xmm5, %xmm2
	addq	$4, %rax
	cmpq	$32768, %rax            ## imm = 0x8000
	jne	LBB0_4
## BB#5:                                ## %middle.block
                                        ##   外部循环:for (unsigned i = 0; i < 100000; ++i)
	paddq	%xmm3, %xmm2
	pshufd	$78, %xmm2, %xmm0
	paddq	%xmm2, %xmm0
	movd	%xmm0, %r13
	incl	%ebx
	cmpl	$100000, %ebx
	jne	LBB0_3
## BB#6:                                static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
	callq	_clock
	subq	%r14, %rax
	movd	%rax, %xmm0
	punpckldq	LCPI0_2(%rip), %xmm0 ## xmm0 = xmm0[0],mem[0],xmm0[1],mem[1]
	subpd	LCPI0_3(%rip), %xmm0
	haddpd	%xmm0, %xmm0
	divsd	LCPI0_4(%rip), %xmm0
	movq	__ZNSt3__14coutE@GOTPCREL(%rip), %rdi
	callq	__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEd
	movq	%rax, %r14
	movq	(%r14), %rax
	movq	-24(%rax), %rsi
	addq	%r14, %rsi
	leaq	-131136(%rbp), %r15
	movq	%r15, %rdi
	callq	__ZNKSt3__18ios_base6getlocEv

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值