初入职软件工程师的血泪——C语言内存优化

简介: 刚刚开始工作就被前辈训得一塌糊涂,特别是对于优化这一块没有一点潜意思,现在不得不重新学习代码优化。对于优化C代码有很多有效的指导方针,但是对于彻底地了解编译器和你工作的机器依然无法取代,通常,加快程序的速度也会加大代码量。

刚刚开始工作就被前辈训得一塌糊涂,特别是对于优化这一块没有一点潜意思,现在不得不重新学习代码优化。

对于优化C代码有很多有效的指导方针,但是对于彻底地了解编译器和你工作的机器依然无法取代,通常,加快程序的速度也会加大代码量。这些增加的代码也会影响一个程序的复杂度和可读性,这是不可接受的,比如你在一些小型的设备上编程,例如:移动设备、PDA……,这些有着严格的内存限制,于是,在优化的座右铭是:写代码在内存和速度都应该优化。

整型数 / Integers

在我们知道使用的数不可能是负数的时候,应该使用unsigned int取代int,一些处理器处理整数算数运算的时候unsigned int比int快,于是,在一个紧致的循环里面定义一个整型变量,最好这样写代码:

register unsigned int variable_name;

然而,我们不能保证编译器会注意到那个register关键字,也有可能,对某种处理器来说,有没有unsigned是一样的。这两个关键字并不是可以在所有的编译器中应用。记住,整形数运算要比浮点数运算快得多,因为处理器可以直接进行整型数运算,浮点数运算需要依赖于外部的浮点数处理器或者浮点数数学库。我们处理小数的时候要精确点些(比如我们在做一个简单的统计程序时),要限制结果不能超过100,要尽可能晚的把它转化成浮点数。

除法和余数 / Division and Remainder

在标准的处理器中,根据分子和分母的不同,一个32位的除法需要20-140个时钟周期来执行完成,等于一个固定的时间加上每个位被除的时间。

Time (分子/ 分母) = C0 + C1* log2 (分子/分母)     

= C0 + C1 * (log2 (分子) - log2 (分母)).

现在的ARM处理器需要消耗20+4.3N个时钟周期,这是一个非常费时的操作,要尽可能的避免。在有些情况下,除法表达式可以用乘法表达是来重写。比方说,(a/b)>c可以写成a>(cb),条件是我们已经知道b为非负数而且bc不会超过整型数的取值范围。如果我们能够确定其中的一个操作数为unsigned,那么使用无符号除法将会更好,因为它要比有符号除法快得多。

合并除法运算和取余运算 / Combining division and remainder

在一些情况下,除法运算和取余运算都需要用到,在这种情况下,编译器会将除法运算和取余运算合并,因为除法运算总是同时返回商和余数。如果两个运算都要用到,我们可以将他们写到一起

int func_div_and_mod (int a, int b) { 
     return (a / b) + (a % b);
}

除数是2的幂的除法和取余 / Division and remainder by powers of two

如果除法运算中的除数是2的幂,我们对这个除法运算还可以进一步优化,编译器会使用移位运算来进行这种除法运算。所以,我们要尽可能调整比例为2的幂(比方说要用64而不用66)。如果是无符号数,它要比有符号的除法快得多。

typedef unsigned int uint;
uint div32u (uint a) {
     return a / 32;
}
int div32s (int a) {
     return a / 32;
}

这两种除法都会避免调用除法函数,另外,无符号的除法要比有符号的除法使用更少的指令。有符号的除法要耗费更多的时间,因为这种除法是使最终结果趋向于零的,而移位则是趋向于负无穷。

取模运算的替换 / An alternative for modulo arithmetic

我们一般使用取余运算进行取模,不过,有时候使用 if 语句来重写也是可行的。考虑下面的两个例子:

uint modulo_func1 (uint count)
{
    return (++count % 60);
}
uint modulo_func2 (uint count)
{
    if (++count >= 60)
        count = 0;
    return (count);
}

第二个例子要比第一个更可取,因为由它产生的代码会更快,注意:这只是在count取值范围在0 – 59之间的时候才行。

但是我们可以使用如下的代码(笔者补充)实现等价的功能:

uint modulo_func3 (uint count)
{
    if (++count >= 60)
        count %= 60;
    return (count);
}

使用数组索引 / Using array indices

假设你要依据某个变量的值,设置另一个变量的取值为特定的字符,你可能会这样做:

switch(queue) {
    case 0 :   letter = 'W';
        break;
    case 1 :   letter = 'S';
        break;
    case 2 :   letter = 'U';
        break;
}

或者这样:

if(queue == 0)
    letter = 'W';
else if ( queue == 1 )
    letter = 'S';
else
    letter = 'U';

有一个简洁且快速的方式是简单的将变量的取值做成一个字符串索引,例如:

static char *classes = "WSU";
letter = classes[queue];

数据优化暂时先写这么多,请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

目录
相关文章
|
28天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
339 0
|
13天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
26 3
|
29天前
|
编译器 程序员 C语言
深入C语言:动态内存管理魔法
深入C语言:动态内存管理魔法
|
1月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
1月前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
16天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
15 0
|
25天前
|
C语言
保姆级教学 - C语言 之 动态内存管理
保姆级教学 - C语言 之 动态内存管理
16 0
|
29天前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
29天前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
39 0
下一篇
无影云桌面