主要探讨如何使用几种不同类型的程序优化技术,使程序运行更加快。
主要结合硬件解释了如何优化,这章不必深入追究,追究也不会明白。
必须写出清晰简洁的代码,这样左不仅为了程序员能够看懂代码,也是为了在检查代码时,别人能够读懂和理解代码。
不依赖于目标机器特性的优化
选择合适的算法和数据结构是前提,然后再进行1、2、3的基本优化。
1、消除循环的低效率
消除循环的条件测试中一些计算结果不会改变的计算。这种很简单的方法称为代码移动。编译器自己也会尽可能代码移动,但是可能调用函数具有副作用那么优化移动失败,这种情况下必须显示地帮助编译器完成代码移动,增加程序性能
2、消除不必要的过程调用
过程调用,创建栈帧会造成不小的开销,所以可以不用过程调用的地方尽量不用过程调用,直接内联即可。
3、消除不必要的存储器引用
引入临时变量(编译器会分配寄存器存储,寄存器访问快速)来存储结果,最后计算结果放回数组或全全局变量或传入地址操作,可以加速程序性能。
int strlen(const char *s)//只需要一个const,表示s不能改变s指向的数值,后面一个const不需要,因为s是形参,是实参的压栈,在这里没必要保护形参
{
int len;
while(*s++ != '\0')
len++;
return len;
}//大写字母比小写字母数字小32
void lower(char *s)
{
int i;
for(i = 0 ; i < strlen(s) ; i++ ){
if(s[i] >= 'A' && s[i] <= 'Z')
s[i] += ('a' - 'A');
}
}
void lower1(char *s)
{
int i;
int len = strlen(s);//这句话就是简单的代码移动,对于大字符串,这相当有用,
//写代码就应该时刻记住这种代码移动,增加性能。时刻记住大数据。
for(i = 0 ; i < len ; i++ ){
if(s[i] >= 'A' && s[i] <= 'Z')
s[i] += ('a' - 'A');
}
}
#define IDENT 1//向量操作初始值
#define OP *//乘
typedef struct {
long int len;
data_t *data;
}vec_rec, *vec_ptr;
vec_ptr new_vec(long int len)//返回长度为len的向量
{
vec_ptr result = (vec_ptr) malloc(sizeof(vec_rec));//分配一个结构体头,指向存储向量的一维数组
if (!result)
return NULL; /* */
result->len = len;
result->allocated_len = len;
if (len > 0) {
data_t *data = (data_t *)calloc(len, sizeof(data_t));//分配一维数组
if (!data) {
free((void *) result);
return NULL; /* */
}
result->data = data;
}
else
result->data = NULL;
return result;//返回头指针,头指针和存储向量的地方,全部动态分配.
}
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;
}
long int vec_length(vec_ptr v)
{
return v->len;
}
data_t *get_vec_start(vec_ptr v)//返回向量首地址
{
return v->data;
}
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;
}
}
void combine2(vec_ptr v, data_t *dest)//消除了循环中的低效率部分,减少了条件判断中函数的调用
{
long int i;
long int length = vec_length(v);
*dest = IDENT;
for (i = 0; i < length; i++) {
data_t val;
get_vec_element(v, i, &val);
*dest = *dest OP val;
}
}
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];
}
}
void combine4(vec_ptr v, data_t *dest)//消除了循环中不必要的内器读写(dest)。通过分配局部变量acc(此时必定分配在寄存器)。最后仅仅一次写搞定
{
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;
}
对于上述combine3和combine4,编译器不会自动将combine3优化为combine4,因为由于存储器的别名,将导致二者函数调用计算的结果不一样。
combine3(v , get_vec_start(v));//目的地址是第一个元素
combine4(v , get_vec_start(v));//目的地址是第一个元素
//二者产生结果明显不一样
依赖于目标机器特性的优化
这里主要涉及循环展开(减少循环迭代次数)等,一些操作依赖于处理器体系结构,相对比较复杂,暂时不用考虑。
小结
明白一些程序的优化,更复杂和体系结构有关的暂时先不用考虑。优化的优先原则就是选择好的数据结构和算法。