以前学过一阵子汇编,后来又忘记了不少,最近翻起来,又发现在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强制使用内部连接的函数可以进行代码的优化,不然编译器默认的会把函数定义为外部连接。


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