# 3.8.4 定长数组
C 语言编译器能够优化定长多维数组上的操作代码。这里我们展示优化等级设置为 -O1 时 GCC 采用的一些优化。假设我们用如下方式将数据类型 fix_matrix 声明为 16 × 16 的整型数组:
#define N 16
typedef int fix_matrix[N][N];
(这个例子说明了一个很好的编码习惯。当程序要用一个常数作为数组的维度或者缓冲区的大小时,最好通过 #define 声明将这个常数与一个名字联系起来,然后在后面一直使用这个名字代替常数的数值。这样一来,如果需要修改这个值,只用简单地修改这个 #define 声明就可以了。)
图 3-37a 中的代码计算矩阵 A 和 B 乘积的元素 i,k,即 A 的行 i 和 B 的列 k 的内积。GCC 产生的代码(我们再反汇编成 C),如图 3-37b 中函数 fix_prod_ele_opt 所示。这段代码包含很多聪明的优化。它去掉了整数索引 j,并把所有的数组引用都转换成了指针间接引用,其中包括(1)生成一个指针,命名为 Aptr,指向 A 的行 i 中连续的元素;(2)生成一个指针,命名为 Bptr,指向 B 的列 k 中连续的元素;(3)生成一个指针,命名为 Bend,当需要终止该循环时,它会等于 Bptr 的值。Aptr 的初始值是 A 的行 i 的第一个元素的地址,由 C 表达式 &A[i][0] 给出。Bptr 的初始值是 B 的列 k 的第一个元素的地址,由 C 表达式 &B[0][k] 给出。Bend 的值是假想中 B 的列 j 的第 (n + 1) 个元素的地址,由 C 表达式 &B[N][k] 给出。
下面给出的是 GCC 为函数 fix_prod_ele 生成的这个循环的实际汇编代码。我们看到 4 个寄存器的使用如下:%eax 保存 result,%rdi 保存 Aptr,%rcx 保存 Bptr,而 %rsi 保存 Bend。
/* Compute i,k of fixed matrix product */
int fix_prod_ele(fix_matrix A, fix_matrix B, long i, long k) {
long j;
int result = 0;
for (j = 0; j < N; j++)
result += A[i][j] * B[j][k];
return result;
}
a)原始的 C 代码
/* Compute i,k of fixed matrix product */
int fix_prod_ele_opt(fix_matrix A, fix_matrix B, long i, long k) {
int *Aptr = &A[i][0]; /* Points to elements in row i of A */
int *Bptr = &B[0][k]; /* Points to elements in column k of B */
int *Bend = &B[N][k]; /* Marks stopping point for Bptr */
int result = 0;
do { /* No need for initial test */
result += *Aptr * *Bptr; /* Add next product to sum */
Aptr ++; /* Move Aptr to next column */
Bptr += N; /* Move Bptr to next row */
} while (Bptr != Bend); /* Test for stopping point */
return result;
}
b)优化过的 C 代码
图 3-37 原始的和优化过的代码,该代码计算定长数组的矩阵乘积的元素 i,k。编译器会自动完成这些优化
int fix_prod_ele_opt(fix_matrix A, fix_matrix B, long i, long k)
A in %rdi, B in %rsi, i in %rdx, k in %rcx
1 fix_prod_ele:
2 salq $6, %rdx Compute 64 * i
3 addq %rdx, %rdi Compute Aptr = x_A + 64i = &A[i][0]
4 leaq (%rsi,%rcx,4), %rcx Compute Bptr = x_B + 4k = &B[0][k]
5 leaq 1024(%rcx), %rsi Compute Bend = x_B + 4k + 1024 = &B[N][k]
6 movl $0, %eax Set result = 0
7 .L7: loop:
8 movl (%rdi), %edx Read *Aptr
9 imull (%rcx), %edx Multiply by *Bptr
10 addl %edx, %eax Add to result
11 addq $4, %rdi Increment Aptr ++
12 addq $64, %rcx Increment Bptr += N
13 cmpq %rsi, %rcx Compare Bptr:Bend
14 jne .L7 If !=, goto loop
15 rep; ret Return
练习题 3.39 利用等式 3.1 来解释图 3-37b 的 C 代码中 Aptr、Bptr 和 Bend 的初始值计算(第 3~5 行)是如何正确反映 fix_prod_ele 的汇编代码中它们的计算(第 3~5 行)的。
练习题 3.40 下面的 C 代码将定长数组的对角线上的元素设置为 val:
/* Set all diagonal elements to val */
void fix_set_diag(fix_matrix A, int val) {
long i;
for (i = 0; i < N; i++)
A[i][i] = val;
}
当以优化等级 -O1 编译时,GCC 产生如下汇编代码:
void fix_set_diag(fix_matrix A, int val)
A in %rdi, val in %rsi
1 fix_set_diag:
2 movl $0, %eax
3 .L13:
4 movl %esi, (%rdi,%rax)
5 addq $68, %rax
6 cmpq $1088, %rax
7 jne .L13
8 rep; ret
创建一个 C 代码程序 fix_set_diag_opt,它使用类似于这段汇编代码中所使用的优化,风格与图 3-37b 中的代码一致。使用含有参数 N 的表达式,而不是整数常量,使得如果重新定义了 N,你的代码仍能够正确地工作。