常用的性能优化技巧

注意:本文针对的主要是c/c++语言,不同语言由于机制不同,会出现不适用的情况。
1.二维数组尽量按行读取
我们知道二位数组实际上是数组的数组,二维数组的每一低维实际上是一个一维数组,而一维数组在内存中的位置是连续的,意味着减少了内存寻址的时间,同时便于处理器缓存数据,减少了缓存不命中的几率。

下面是一段测试代码来说明这个问题:

#include <iostream>
#include <omp.h>
#include <time.h>
#include <Windows.h>
using namespace std;

const long long int SIZEOFMAT = 15000;
int mat_a[SIZEOFMAT][SIZEOFMAT];

void creat_mat()
{
    for(int i=0;i<SIZEOFMAT;i++)
        for(int j=0;j<SIZEOFMAT;j++)
        {
            mat_a[i][j] = j;
        }

}

void readMatByRow()
{
    for(int i=0;i<SIZEOFMAT;i++)
    {
        int sum = 0;
        for(int j=0;j<SIZEOFMAT;j++)
        {
            sum += mat_a[i][j];
        }
    }
}

void readMatBycol()
{
    for (int i = 0; i<SIZEOFMAT; i++)
    {
        int sum = 0;
        for (int j = 0; j<SIZEOFMAT; j++)
        {
            sum += mat_a[j][i];
        }
    }
}


int main()
{
    creat_mat();
    DWORD start, end;
    start = GetTickCount();
    readMatByRow();
    end = GetTickCount();
    cout << "row" << endl << end - start << endl;
    Sleep(10);
    start = GetTickCount();
    readMatBycol();
    end = GetTickCount();
    cout << "col" << endl << end - start << endl;

    return 0;
}

运行结果是:
这里写图片描述
可以看到速度相差3倍!

事实上,由于我们的任务是数组相加,我们还可以将循环展开来进一步提高效率,如将按行读取数组那部分改为:

void readMatByRowWithLoopUnrolling()
{
    for (int i = 0; i<SIZEOFMAT; i++)
    {
        int sum = 0;
        for (int j = 0; j<SIZEOFMAT; j+=5)
        {
            sum += mat_a[i][j];
            sum += mat_a[i][j+1];
            sum += mat_a[i][j+2];
            sum += mat_a[i][j+3];
            sum += mat_a[i][j+4];
        }
    }
}

这是运行结果,LoopUnrolling那行是循环展开后的用时,Loop那行是循环未展开的用时(均为按行读取矩阵),可以看到速度差距还是相当明显的。
这个的原理也很容易想到,减少了判断次数,增加了处理器处理流水线的能力,当然还可以展开外层循环,这个就读者自己去尝试了。
这里写图片描述
对于循环来说,可操作性还是比较强,另外也没有固定的套路,但有一点原则就是尽量不让寄存器溢出的基础上尽量充分的运用它,比如有一个循环,循环体的代码量很大,导致寄存器溢出,这时候,我们可以把没有数据依赖的部分分拆循环,用多个循环来执行它,以提高效率,另外我们尽量避免把判断放在循环体里,减少分支预测失误的不利影响。
还有一种骚操作,就是利用条件复制指令移除分支,例如以下一段代码:

if(a > 0)
{
    x = a;
}
else
{
    x=b;
}

可改为:

x = (a>0 ? a : b );

另外还有种优化思路,那就是定义数组时用short int, 因为我们发现数组的值都没有超过short int的范围,不过在这个情境下,这样对效率的提升比较有限,然而对空间的优化还是相当明显的。
2.
使用条件编译,由于宏条件在编译时已经确定,可以帮助编译器直接忽略不成立的分支,提高运行效率,不过这也有一个问题,就是只能使用多个程序编译。

3.对于编译器自身来说,选择合理的编译优化选项(比如 cl的od/o1/o2/ox),另外比如指定指令集(我这部分还需要学习,过几天开个博客专门说这个),再是降低编译器的优化难度,比如减少全局变量的数量(不过这和有些编程原则冲突,需要合理的使用),以及避免存储器别名,下面我们详细的说一说存储器别名的问题,看下面一段代码:

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

假如我们是编译器,我们可以尝试将函数简化成:

int f(int *a,int *b)
{   
    int temp = *b;
    *a += 2*temp;
}

上面这段代码,如果a,b之间存在存储器别名,即a,b指向的是同一段内存,那么很明显结果是错的,而应该变为如下代码:

int f(int *a, int *b)
{
    int temp= *b;
    *a= 4 * temp;
}

所以编译器为了保险起见便不再优化,但是我们可以通过restrict手工指定指针不是存储器别名,在vs中使用宏RESTRICTED_POINTER来定义,如下:

int f(int * RESTRICTED_POINTER a, int * RESTRICTED_POINTER b)
{
    *a += *b;
    *a += *b;
}

以提供给编译器优化空间。

4.
适当的使用inline(内联函数),但是注意它会引起空间上的开销。

注意在X86的处理器上,函数的参数优先存入寄存器中,如果你的函数参数是一个巨大的结构体,请使用结构体的指针来作为参数传递而不是整个结构体(这个的前提是你只调用这个结构体的一部分,如果当然如果你需要调用整个结构体,那么用指针传递会造成比较大的开销)
说到结构体,我们也可以采用一些方式来对我们结构体进行优化,比如,对结构体进行按字节对齐,在vs中命令为:#pragma pack(push,size)
用#pragma pack(pop)来还原默认
另外还有一个小技巧,声明结构体时,大数据类型在前,小数据类型在后。

5.
性能比较:
位运算 1周期
乘法 3周期
除法 10+周期
模运算 几十上百

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值