程序性能优化
这里描述了两个会阻止编译器优化的场景。
例子1. 将C风格的string转换成小写
void lower(char *s)
{
size_t i;
for (i = 0; i < strlen(s); i++)
{
if (s[i] > 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
}
这段代码的问题在于strlen(s)被重复运算
strlen会遍历字符串,直到找到空字符为止,函数为线性复杂度,lower算法复杂度为O(n^2)
编译器无法优化函数调用的代码
- 不确定函数中是否会有副作用。
- 函数不一定会返回相同的值。
在例子1中,循环体会修改s的值,编译器无法确保strlen(s)不变
改为如下:
void lower(char *s)
{
size_t i;
size_t len = strlen(s);
for (i = 0; i < len; i++)
{
if (s[i] > 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
}
函数复杂度被修改为O(n)
例子2. 计算矩阵每行的和
/ * Sum rows is of n X n matrix a and store in vectorb */
void sum_rows1(double *a, double *b, long n)
{
long i, j;
for (i = 0; i < n; i++)
{
b[i] = 0;
for (j = 0; j < n; j++)
{
b[i] += a[i * n + j];
}
}
}
这段代码表面上看上去没问题(当然,可以移动i * n到外部),我们看一下内循环汇编后的代码
# sum_rows1 inner loop
.L4:
movsd (%rsi, %rax, 8) %xmm0 # FP load
addsd (%rdi), %xmm0 # FP add
movsd %xmm0, (%rsi, %rax, 8) # FP store
addq $8, %rdi
cmpq %rxc, %rdi
jne .L4
每次循环都需要从内存中读取b[i],做完加法后写回内存。这里每一次取b[i]的值,与上一次写回b[i]的值是一样的。数据在内存和寄存器之间来回传送。
编译器假设存在内存别名
在例子中,b与a的地址有重合的部分,对b[i]的写入可能会影响a[i]的值,从而影响结果求和,所以需要编译器不能优化这段代码,每一次都需要对内存进行读写。
这里可以引入一个局部变量存储每行累加的值,在换行的时候将值赋给b[i]
/ * Sum rows is of n X n matrix a and store in vectorb */
void sum_rows1(double *a, double *b, long n)
{
long i, j;
for (i = 0; i < n; i++)
{
double val = 0;
for (j = 0; j < n; j++)
{
val += a[i * n + j];
}
b[i] = val;
}
}
内循环对应的汇编代码:
# sum_rows2 inner loop
.L10:
addsd (%rdi), %xmm0 # FP load + add
addq $8, %rdi
cmpq %rax, %rdi
jne .L10
进行测试后发现性能有略微的提升,并没有例子1这么明显。