程序优化示例
初始代码如下:
#define IDENT 0
#define OP +
typedef int data_t;
typedef struct {
long int len;
data_t *data;
} vec_rec, *vec_ptr;
// Create vector of specified length
vec_ptr new_vec(long int len)
{
// Allocate header structure
vec_ptr result = (vec_ptr) malloc(sizeof(vec_rec));
if (!result) return NULL; // couldn't allocate storage
result->len = len;
// Allocate array
if (len > 0)
{
data_t *data = (data_t *) calloc(len, sizeof(data_t));
if (!data)
{
free((void *) result);
return NULL; // couldn't allocate storage
}
result->data = data;
} else {
result->data = NULL;
}
return result;
}
/*
* Retrieve vector element and store at dest.
* Return 0 (out of bounds) or 1 (successful)
*/
int get_vec_element(vec_ptr v, long int index, data_t *dest)
{
if (index < 0 || index >= v->len) return 0;
*dest = v->data[index];
return 1;
}
// Return length of vector
long int vec_length(vec_ptr v)
{
return v->len;
}
/* Implementation with maximum use of data abstraction */
void combine1(vec_ptr v, data_t *dest)
{
long int i;
*dest = IDENT;
for (i = 0; i < vec_length(v); ++i)
{
data_t val;
get_vec_element(v, i, &val);
*dest = *dest OP val;
}
}
在我们的讲述中,会对这段代码进行一系列的变化,写出这个合并函数的不同版本。
1 消除循环的低效率
可以观察到,过程 combine1
调用函数 vec_length
作为 for
循环的测试条件,而每次循环迭代时都必须对测试条件求值。另一方面,向量的长度并不会随着循环的进行而改变。因此,只需计算一次向量的长度,然后在我们的测试条件中都使用这个值。
// Move call to vec_length out of loop
void combine2(vec_ptr v, data_t *dest)
{
long int i;
long int length = vec_legth(v);
*dest = IDENT;
for (i = 0; i < length; ++i)
{
data_t val;
get_vec_element(v, i, &val);
*dest = *dest OP val;
}
}
2 减少过程调用
像我们看到的那样,过程调用会带来相当大的开销,而且妨碍大多数形式的程序优化。从 combine2
的代码可以看出,每次循环迭代都会调用 get_vec_element
来获取下一个向量元素。对每个向量引用,这个函数都要把向量索引 i
与循环边界做比较,很明显会造成低效率.在处理任意数组访问时,边界检查可能是个很有用的特性,但是对 combine2
代码的简单分析表明所有的引用都是合法的。
data_t *get_vec_start(vec_ptr v)
{
return v->data;
}
// Direct access to vector data
void combine3(vec_ptr v, data_t *dest)
{
long int i;
long int length = vec_length(v);
data_t *data = get_vec_start(v);
*dest = IDENT;
for (i = 0; i < length; ++i)
{
*dest = *dest OP data[i];
然而,得到的性能提高出乎意料的普通,那到底是为什么 combine2
中反复的边界检查不会让性能更差 ? (深入理解操作系统,分支预测,见5.11.2节)
3 消除不必要的存储器引用
// Accumulate result in local variable
void combine4(vec_ptr v, data_t *dest)
{
long int i;
long int length = vec_length(v);
data_t *data = get_vec_start(v);
data_t acc = IDENT;
for (i = 0; i < length; ++i)
{
acc = acc OP data[i];
}
*dest = acc;
}