代码优化向导--第一部分

    这是一篇在网上看到的技术文章,它把一个原来需要6000毫秒的代码段优化到400毫秒,这种优化效果让我震撼,所以我决定把它翻译共享出来。

    下面是原文链接:http://www.codeproject.com/Articles/381630/Code-optimization-tutorial-Part-1

简介:

    这篇文章是尝试把代码优化技术介绍给软件开发者。为些,我们将探究各种优化的方法。
    第一步,我选取一段容易理解的算法代码段,并在上面运用各种不同的优化方法。
    我们要解决的问题是3n+1猜想(关于这个问题的详细解释大家可以点这个链接 3n+1问题详细),
    我们执行一个从1到1000000的循环,每个数都套用这个函数:
    
    直到n变成1,我们再执行下一个数,我们不需要从键盘上读取任何数据程序就可以打印结果,并且我们可以计算出运行的时间,用来做测试的设备是:   AMD Athlon 2 P340 Dual Core 2.20 GHz, 4 GB of RAM, Windows 7 Ultimate x64。测试的语言是:C#和C++
    代码的第一个版本:为1到1000000中的每个数执行上面的算法,算法会产生一系列的数字,直到等于1时才会停止,归1的步骤数将会被记录下来,并且最大的步骤数将会被确定下来。
C++代码段:
for (int i = nFirstNumber; i < nSecondNumber; ++i)
{
    int nCurrentCycleCount = 1;
    long long nNumberToTest = i;
    while (nNumberToTest != 1)
    {
        if ((nNumberToTest % 2) == 1)
        {
            nNumberToTest = nNumberToTest * 3 + 1;
        }
        else
        {
            nNumberToTest = nNumberToTest / 2;
        }
        nCurrentCycleCount++;
    }
    if (nCurrentCycleCount > nMaxCycleCount)
    {
        nMaxCycleCount = nCurrentCycleCount;
    } 
}  
C#代码段:
for (int i = FirstNumber; i < SecondNumber; ++i)
{
    int iCurrentCycleCount = 1;
    long iNumberToTest = i;
    while (iNumberToTest != 1)
    {
        if ((iNumberToTest % 2) == 1)
        {
            iNumberToTest = iNumberToTest * 3 + 1;
        }
        else
        {
            iNumberToTest = iNumberToTest / 2;
        }
        iCurrentCycleCount++;
    }
    if (iCurrentCycleCount > MaxCycleCount)
    {
        MaxCycleCount = iCurrentCycleCount;
    }
} 

    我用Debug和Release模式以及32位和64位分别执行了这段代码,然后我每次都运行100次取平均值(单位是ms)。下面是运行的结果:
 C++ DebugC++ ReleaseC#DebugC# Release
x86 version6882637463585109
x64  versiong10208121890742

    我们从表里面看到的第一点是,32位的程序要比64位的慢5到7倍,因为x64设计是一个寄存器可以存储一个long long数据,而x86而需要两个寄存器,所以在后面我们不再研究32位的程序。第二点是,Debug和Release所用的时间差距很大,特别是C#。第三点是,通过观察,C#编译器貌似比C++编译器在优化方面更胜一筹。
我提供的第一个优化动作是与数学操作符有关的,我用非传统的方式替换传统的方式。我们可以看到上面的代码里面只有三个复杂的数学操作符:%,*,/。首先我要优化的是%2操作,我们发现只要与0x1做&操作就可以判断一个数是否是奇数。所以当我们执行下面的替换时:
if ((nNumberToTest % 2) == 1)
替换成:
if ((nNumberToTest & 0x1) == 1)
下面是新的结果表:
C++ DebugC++ ReleaseC# DebugC# Relase
9225601641714

    我们发现,C++ Release版本得到了很大的优化,Release和Debug版本的优化程序不同让我相信,在Release版本下编译器会删除多余的指令。C#则好像没有太大的优化。下一个优化的动作是/2操作,我们发现用位操作>>1与/2操作得到的是相同的效果, 所以当我们执行下面的替换时
nNumberToTest = nNumberToTest / 2;
替换成:
nNumberToTest = nNumberToTest >> 1;
下面是新的结果:
C++ DebugC++ ReleaseC# DebugC# Relase
8215551432652
    我们发现C++ Debug, C# Debug, C# Release有差不多65到200毫秒的优化,而C++ Release版本则好像没有优化一样,那是因为在这种模式下编译器已经做了这个转换操作。最我们能做的变换只能是把*换成+操作, 所以当我们执行下面的替换时
nNumberToTest = nNumberToTest * 3 + 1; 
替换成:
nNumberToTest = nNumberToTest + nNumberToTest + nNumberToTest + 1; 
下面是新的结果:

C++ DebugC++ ReleaseC# DebugC# Relase
8205481535629

    优化最大的是C# Rlease版本,而C++ Release版本次之。而C# Debug的性能不升反降,这是因为当前版本相比上个版本执行了更多的指令,而且这时编译没有优化这些指令。这是最后一个可以优化的数学操作符,而代码中还有一些传统代码,为了确定编译器是否会自动产生一些传统的移动指令,我决定用三元运算符?:来替换if语句。为了能执行上面的优化操作我们要修改代码段:如果数字是奇数,它的就要除以2,如果是偶数,它就要加上2n+1。根据修改我们可以得到下面的初始化函数形式:

用上面的等式,我们可以把两步操作合并成一步。我们要重写算法以便我们可以计算下一步的数值,假设现在的数是个偶数,然后我们存储这个数的最后一个字节,如果这个值为真,我们就增加当前循环的次数,并且把number+1加到下一个数值上(这个优化操作很重要,因为它与我下一篇要讲的sse优化有很大的关连)。
if ((nNumberToTest % 2) == 1)
{
 nNumberToTest = nNumberToTest * 3 + 1;
}
else
{
 nNumberToTest = nNumberToTest / 2;
}
nCurrentCycleCount++;
替换成:
int nOddBit = nNumberToTest & 0x1;
long long nTempNumber = nNumberToTest >> 1; 
nTempNumber += nOddBit?nNumberToTest + 1:0;
nCurrentCycleCount += nOddBit?2:1;
nNumberToTest = nTempNumber;
下面是新的结果(用三元操作符替换if语句):

C++ DebugC++ ReleaseC# DebugC# Relase
11954621565752
(译者注:上面的替换代码看了许久才看懂,因为原文的解析也很少,都不知道原作者是懒得说还是不屑说。没办法,最后我用9,10这两个数字做单步调试才发现点思路,原来碰到奇数时,直接跳过一步,因为奇数的下一个数(3n+1)肯定是个偶数,它必定会做一个除2的操作,所以奇数的下下个数肯定就是(n/2 + n + 1),但是我这样的解释也缺乏严谨性,亏我大学还是数学本科。哎,希望有高人给我更加严谨的解释)

总结:

1.取模和除法操作会耗用较多的时间,应该想办法用别的操作来替换它们
2.仔细分析问题,并找出一个可以替代的解决方案来解决这个问题
3.尽量用别的方法来替换if语句,如果if语句只是在基于一种情况下得到几个不同的值
下篇文章主要在C#和C++环境下讲解“怎么让你的程序更快”
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值