汇编角度来分析C++优化

以前学过一阵子汇编,后来又忘记了不少,最近翻起来,又发现在C++层其实有很多可以编程习惯,可以根据汇编原理进行优化。
//=========================================

1:多使用float.

为了让编译器产生更好的3DNow!或SSE指令代码,我们可以必须确定浮点型变量和表达式是float型的,为避免float自动转换为double,我们声明时需要强调,并在后面加以f后缀。

//=========================================

2:适时使用无符号整形

1)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;
指令配对,代码运行较快

2)对整形求商求余时,使用无符号整形会比较快,我们也以汇编代码查看分析
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;

3)同样,在循环计数,数组下标时,尽可能的使用无符号整形,会提高代码运行速度。

//=========================================

3:多使用for,少使用while

我们依旧是看反汇编代码。
while( 1 );                      mov eax, 1
                                 test eax, eax
                                 je temp+23h
                                 jmp temp+18h
而for的则是
for( ; ; );                      jmp temp=23h

//=========================================

4:多使用数组,少使用指针

我这条建议恐怕要受到广大C++程序员的责骂与非议,而实际上编译器是很难对指针进行优化的,因为默认的编译器会假设指针可以访问任何的内存地址。而我们使用操作符[]来访问数组的话,会让编译器减少产生不安全代码的问题

//=========================================

5:循环的优化

1)人工拆分小循环
为了充分的利用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];

2)小循环写外,大循环写里,这样可以避免断掉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:
     }  
}

同样的道理,循环与判断嵌套时,我们也可以将判断放外,循环放内比较好,避免判断打断循环指令。

//=========================================

6:避免使用无必要的读写依赖

当数据保存到内存时存在读写依赖,尽管AMD Athlon等CPU有加速读写依赖延迟的硬件,允许将保存的数据在写入前读取出来,但是,我们手工将读写保存去除的话,代码性能能有很大的提升。
我们的做法通常是牺牲一定的空间在寄存器中创建一个临时变量。

//=========================================

7:优化Switch

Switch可能使用多种算法进行优化,其中最普通的是跳转表和比较链。我推荐大家将最有可能的case放在第一位,此时比较链会得到一定性能的提高,另外,case尽量使用比较小的连续整数,因为此时编译器会将Switch转化为跳转表。

//=========================================

8:尽量多的使用常量const

因为C++标准规定,若一个const对象的地址不被获取,编译器可以不对它分配存储空间。

//=========================================

9:多使用内联函数和静态函数

当然,内联函数不是越多越好,它是以代码膨胀以代价的加速,根据需要吧。而static强制使用内部连接的函数可以进行代码的优化,不然编译器默认的会把函数定义为外部连接。

//=========================================

困了,今天就先分析到这里。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值