汇编角度来分析C++优化
以前学过一阵子汇编,后来又忘记了不少,最近翻起来,又发现在C++层其实有很多可以编程习惯,可以根据汇编原理进行优化。
多使用float.
为了让编译器产生更好的3DNow!或SSE指令代码,我们可以必须确定浮点型变量和表达式是float型的,为避免float自动转换为double,我们声明时需要强调,并在后面加以f后缀。
适时使用无符号整形
- int转换float/double类型时,建议使用有符号整形,因为X86构架中,提供了从int转换为float/double类型的指令,而没有提供unsigned int转换为浮点型的指令,我们看下编译器反汇编后的汇编代码。
double x; mov [temp + 4], 0
unsigned int x; mov eax, i
x = i; mov [temp], eax
flid qword ptr [temp]
fstp qword ptr [x]
这段代码不仅指令数多,而且因为指令配对失败造成的FLID指令会被延迟执行,代码运行较慢。 而使用无符号整形的话,会快的多,反汇编如下
double x; flid qword ptr [i]
int i; flid qword ptr [x]
x = i;
指令配对,代码运行较快
- 对整形求商求余时,使用无符号整形会比较快,我们也以汇编代码查看分析
int i; mov eax, i
i = i / 4; cdq
and edx, 3
and edx, edx
sar eax, 2
mov i, eax
而当我们使用无符号整形时
unsigned int i; shr i, 2
i = i / 4;
- 同样,在循环计数,数组下标时,尽可能的使用无符号整形,会提高代码运行速度。
多使用for,少使用while
我们依旧是看反汇编代码。
while( 1 ); mov eax, 1
test eax, eax
je temp+23h
jmp temp+18h
而for的则是
for( ; ; ); jmp temp=23h
多使用数组,少使用指针
我这条建议恐怕要受到广大C++程序员的责骂与非议,而实际上编译器是很难对指针进行优化的,因为默认的编译器会假设指针可以访问任何的内存地址。而我们使用操作符[]来访问数组的话,会让编译器减少产生不安全代码的问题
循环的优化
- 人工拆分小循环 为了充分的利用CPU的指令缓存,我们可以把一些很小的循环手工拆开,虽然这样写出的代码会显得有些冗余“低水平”,而这样做的确可以提高性能。例如
// 3D转化:把矢量V和4x4矩阵M相乘
for (i = 0; i < 4; i ++)
{
r[i] = 0;
for (j = 0; j < 4; j ++)
{
r[i] += M[j][i]*V[j];
}
}
我们可以将其拆分为
// 3D转化:把矢量V和4x4矩阵M相乘
r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];
r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];
r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];
r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];
- 小循环写外,大循环写里,这样可以避免断掉CPU的连续循环指令。例如
for( unsigned int i=0; i<=10; ++i )
{
for( unsigned int j=0; j<=10000; ++j )
{
// TODO:
}
}
就比下面的好
for( unsigned int j=0; j<=10000; ++j )
{
for( unsigned int j=0; j<=10; ++i )
{
// TODO:
}
}
同样的道理,循环与判断嵌套时,我们也可以将判断放外,循环放内比较好,避免判断打断循环指令。
避免使用无必要的读写依赖
当数据保存到内存时存在读写依赖,尽管AMD Athlon等CPU有加速读写依赖延迟的硬件,允许将保存的数据在写入前读取出来,但是,我们手工将读写保存去除的话,代码性能能有很大的提升。 我们的做法通常是牺牲一定的空间在寄存器中创建一个临时变量。
优化Switch
Switch可能使用多种算法进行优化,其中最普通的是跳转表和比较链。我推荐大家将最有可能的case放在第一位,此时比较链会得到一定性能的提高,另外,case尽量使用比较小的连续整数,因为此时编译器会将Switch转化为跳转表。
尽量多的使用常量const
因为C++标准规定,若一个const对象的地址不被获取,编译器可以不对它分配存储空间。
多使用内联函数和静态函数
当然,内联函数不是越多越好,它是以代码膨胀以代价的加速,根据需要吧。而static强制使用内部连接的函数可以进行代码的优化,不然编译器默认的会把函数定义为外部连接。
困了,今天就先分析到这里。