查看原文
其他

C语言代码优化的一些技巧(三)

正念 嵌入式大杂烩 2021-01-31

点击上方「嵌入式大杂烩」,选择「置顶公众号」第一时间查看编程笔记!

循环展开

简单的循环可以展开以获取更好的性能,但需要付出代码体积增加的代价。循环展开后,循环计数应该越来越小从而执行更少的代码分支。

如果循环迭代次数只有几次,那么可以完全展开循环,以便消除循坏带来的负担。例如:

for(i=0; i<3; i++)
{
    something(i);
}

展开为:

something(0);
something(1);
something(2);

这可以非常可观的节省性能,原因是代码不用每次循环需要检查和增加i的值。

if判断条件的顺序

if的判断条件中概率最大的情况应放在前面。例子:

if (1 == condition)
{

}
else if (2 == condition)
{

}
else
{

}

此处,若condition为1的概率大较大则把if (1 == condition)放在前面。

condition为2概率大较大则把if (2 == condition)放在前面,如:

if (2 == condition)
{

}
else if (1 == condition)
{

}
else
{

}

这里有个小细节:在用if判断某个变量与某个常量是否相等时,可以把常量写在前面变量写在后面,如:

if (2 == condition)

2放在前面,condition放在后面。这样的好处就是当你漏敲了一个=号时,编译器会指出你的这个错误。

尽早退出循环

通常,循环并不需要全部都执行。例如,如果我们在从数组中查找一个特殊的值,一经找到,我们应该尽可能早的断开循环。

例如:如下循环从10000个整数中查找是否存在-99。

found = FALSE;
for(i=0;i<10000;i++)
{
    iflist[i] == -99 )
    {
        found = TRUE;
    }
}

if( found ) 
{
    printf("Yes, there is a -99. Hooray!\n");
}

这段代码无论我们是否查找得到,循环都会全部执行完。更好的方法是一旦找到我们查找的数字就终止继续查询。

把程序修改为:

found = FALSE;
for(i=0;i<10000;i++)
{
    iflist[i] == -99 )
    {
        found = TRUE;
        break;
    }
}

if( found ) 
{
    printf("Yes, there is a -99. Hooray!\n");
}

假如待查数据位于第23个位置上,程序便会执行23次,从而节省9977次循环。

使用位运算替代四则运算

在许多古老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多。

在现代架构中, 位运算的运算速度通常与加法运算相同,但仍然快于乘法运算。所以通常乘以或除以2n可以使用位运算来代替四则运算,如

a = a * 8;
a = a / 8;
a = a % 8;

修改为:

a = a << 3;
a = a >> 3;
a = a & 7;

以空间换时间

在内存比较充足的情况下,可以使用空间来换取时间。比如使用查表法,把一些可能的结果事先保存到表中。例如求阶乘通常的做法是:

long factorial(int i)
{
    if (i == 0)
        return 1;
    else
        return i * factorial(i - 1);
}

若是空间比较足,而且所需的结果都能列举出来,则代码可以修改为:

static long factorial_table[] = {1, 1, 2, 6, 24, 120, 720  /* etc */ };
long factorial(int i)
{
    return factorial_table[i];
}

使用复合赋值语句

增加一个变量的值有两种方式,如:a = a + 5a += 5。存在两种增加一个变量值的方法有何意义呢?

K&R C设计者认为复合赋值符可以让程序员把代码写得更清楚些。另外,编译器可以产生更为紧凑的代码。

现在,a = a + 5和a += 5之间的差别不再那么显著,而且现代的编译器为这两种表达式产生优化代码并无多大问题。

但是,要考虑类似如下的语句:

a[2*(y-6*f(x))] = a[2*(y-6*f(x))] + 5;
a[2*(y-6*f(x))] += 5;

此处a为数组。在第一种形式种,由于编译器无从知道f函数是否具有副作用,所以它必须两次计算数组a的下标表达式的值。

而在第二种形式中,下标表达式只需计算一次,所以第二种形式效率更高。并且,从书写的角度看,第一种形式的下标表达式需要书写两次,而第二种形式只需书写一次。

尽量使循环体内的工作量达到最小化

循环中,随着循环次数的增加,会加大对系统资源的消耗。我们应当确认一些操作是否必须放在循环体内。示例代码:

for (i = 0; i < n; i++)
{
    tmp += i;
    sum = tmp;
}

这是个求和操作,但是这里每循环一次,就要进行一次sum = tmp;操作,这样的写法很浪费资源。这一条语句完全可以移至循环体外:

for (i = 0; i < n; i++)
{
    tmp += i;
}
sum = tmp;

这样,sum = tmp;语句只执行一次,不仅可以调高程序效率,也提高了可读性。同时,我们还可以考虑类似这样的代码是否有必要封装成一个函数供多个地方调用。

End:如果这些技巧对你有帮助的话,欢迎收藏、转发、在看~


猜你喜欢:

C语言代码优化的一些技巧(一)

C语言代码优化的一些技巧(二)


我的个人博客:

https://zhengnianli.github.io


查看往期笔记:

聊天界面输入m获取往期笔记目录

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存