从机器级角度思考如何优化程序的性能

1.优化程序性能的必要性?

2.为什么编译器有优化选项,而我们还要了解如何用我们自己的代码去优化?

3.优化程序性能都有哪些方法?


这边文章我将从以上三个方面去探讨如何从机器级角度去优化程序的性能~


1.优化程序性能的必要性?

我们现在用的个人电脑的性能都是非常高的,执行一个循环100次的块在优化后与优化前可能感受不到优化的效果,那么我们还有优化程序性能的必要吗?设想有以下几种情况:
1.我对优化感兴趣,我想知道优化的原理。
2.嵌入式程序开发的硬件性能相对个人电脑不是那么高,如果你是嵌入式开发工程师,并且在提交给客户的软件版本的性能就差那么十几毫秒,那么我认为你需要了解一下如何优化。
3.我想装B。

我相信总有一款适合你~

2.为什么编译器有优化选项,而我们还要了解如何用我们自己的代码去优化?

我们在编译的时候可能会用到优化选项(-On),如果优化选项过高,编译出来的代码将很难调试,如果优化选项过低,优化效果可能不明显。一般情况下我们在编译工程的时候优化选项都会选择O2级别。但是有一点需要我们程序员明白,编译器给你的代码优化与否,取决于编译器对你代码的分析。如果编译器分析后得出你的代码没有二义性,那么编译器会替你优化代码。但是一般情况下,我认为编译器很难对你的代码进行优化,比如下面的例子:

int sum(int *a, int *b)
{
    *a += *b;
    *a += *b;
    return *a;
}

int sum(int *a, int *b)
{
    *a += 2 * (*b);
    return *a;
}

乍一看这两个接口表达的意思是一样的,并且下面的性能要比上面的性能高,但是很多情况下,如果你用了上面的那个方法,编译器是不给你优化的,为什么?如果a和b指向同一块内存,这两个接口返回的结果是不一样的。因为编译器无法分析出你用这个接口的时候能不能保证两个参数是不是指向同一块内存,所以编译器就不会给你优化。

所以,我们不能完全依赖编译器的优化选项,还需要在我们自己的代码中去尽可能的优化。


3.优化程序性能都有哪些方法?

这里我将简单列出我目前掌握到的代码性能优化方法,并简单的说明,如果大家有什么问题的话,我们可以一起探讨共通学习。

有些背景知识需要简单说明一下:现在的cpu都是流水线式的,也就是说cpu不是等待一个指令完成了才去执行第二个指令的,而是可以并行的。

这是因为指令和指令之间不一定会有数据关联,没有数据关联的影响,指令就可以并行处理了。另外cpu执行指令分为ICU和EU两部分。ICU为指令控制单元,EU为指令执行单元。


1.消除不必要的寄存器引用,用临时变量存放结果代替入参

现有以下两个接口:

int dest[20] = {
1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20
};

void combine_1(int size, int* dest, int* result)
{
for (int i = 0; i < 20; i++){
*result = dest[i] * *result;
}
}

void combine_2(int size, int* dest, int* result)
{
int res = 1;
for (int i = 0; i < 20; i++){
res = dest[i] * res;
}

*result = res;
}

结论:combine_2性能优于combine_1.

为什么2会由于1,请看下面的汇编代码:

*result = dest[i] * *result;
004113E6  mov         eax,dword ptr [i] 
004113E9  mov         ecx,dword ptr [dest] 
004113EC  mov         edx,dword ptr [result] 
004113EF  mov         eax,dword ptr [ecx+eax*4] 
004113F2  imul        eax,dword ptr [edx] 
004113F5  mov         ecx,dword ptr [result] 
004113F8  mov         dword ptr [ecx],eax 


res = dest[i] * res;
0041144D  mov         eax,dword ptr [i] 
00411450  mov         ecx,dword ptr [dest] 
00411453  mov         edx,dword ptr [ecx+eax*4] 
00411456  imul        edx,dword ptr [res] 
0041145A  mov         dword ptr [res],edx 


显然1比2多了两条mov操作。


2.减少过程调用。

也就是说尽量少的进行接口调用。但是这项优化是要付出代价的:这破坏了代码的模块性和抽象性。


3.消除循环低效率,循环条件式不应该调用方法。

例如for(int i = 0; i < get_max(); i++)需要改写成int j = get_max();for(int i = 0;i < j;i++)


下面两个方法为进阶的方法:

4.循环展开。将原来的2n次循环变为n次循环,并且可以用多个临时变量进行并行处理。

void combine_3(int size, int* dest, int* result)
{
int res_1 = 1;
int res_2 = 1;
for (int i = 0; i < 10; i = i + 2){
res_1 = dest[i] * res_1;
res_2 = dest[i + 1] * res_2;
}

*result = res_1 * res_2;
}

我们将combine_2再次进行优化,得到了combine_3,我们可以看一下汇编:

res_1 = dest[i] * res_1;
004114B4  mov         eax,dword ptr [i] 
004114B7  mov         ecx,dword ptr [dest] 
004114BA  mov         edx,dword ptr [ecx+eax*4] 
004114BD  imul        edx,dword ptr [res_1] 
004114C1  mov         dword ptr [res_1],edx 
res_2 = dest[i + 1] * res_2;
004114C4  mov         eax,dword ptr [i] 
004114C7  mov         ecx,dword ptr [dest] 
004114CA  mov         edx,dword ptr [ecx+eax*4+4] 
004114CE  imul        edx,dword ptr [res_2] 
004114D2  mov         dword ptr [res_2],edx 

虽然说combine_3的汇编跟combine_2是一样的,但是我们知道,循环跳转由原来的2n次变为了n次,并且,我们的cpu是流水线式的可以进行并行处理的。

所以combine_3的性能是比combine_2更高的。


5.条件转移指令替换条件指令,减少预测惩罚。比如用条件表达式替换if语句。

cpu在执行指令的时候,为了提高执行效率,会对执行分支进行预测。虽然预测的成功率很高,但是一旦预测失败将收到44个时钟周期的处罚(以core i7为例,预测失败会将

之前预测得到的值清空,再重新执行另外的分支)。所以预测惩罚还是很影响效率的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值