优化程序性能的几个方法(来自于《深入理解计算机系统》)

简介:

这部分的代码出自《深入理解计算机系统》(CS:APP)第五章,其目的是通过手工改变代码结构,而不是算法效率和数据结构优化,提高执行效率。有些编译器在某些优化选项下可能会做出类似的改动。

  为了便于以后的查阅和使用,本文进行了摘录和简要分析,其中包含了一些个人理解。对于更深层次的原理如汇编、处理器结构等请参考原书。

  大致地,越靠后的代码性能越好,版本6和7性能近似,版本6略好一些。二者均能达到版本1性能的10倍左右。

  示例演示对于一个向量的所有元素完成一个运算。这个运算可以是所有元素的累加

#define IDENT 0
#define OP +

  或这所有元素的累计乘积 

#define IDENT 1
#define OP *

 

  data_t代表一种数据类型,在这个示例中可以是int、float、double

typedef struct {
    long int len;
    data_t *data;
} vec_rec, *vec_ptr;

   对于vec_rec,有以下操作

创建向量

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;
}
vec_ptr new_vec(long int len)

 

根据索引号获取向量元素

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;
}
int get_vec_element(vec_ptr v, long int index, data_t *dest)

 

获得向量长度

long int vec_length(vec_ptr v)
{
    return v->len;
}
long int vec_length(vec_ptr v)

 

用红色标记各个版本的改变。

 


版本1:初始版本

复制代码
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;
    }
}
复制代码

  如果不考虑错误的参数传递,这是一个可用版本,有着巨大的优化空间。

 


 

版本2:消除循环的低效率

  并非不使用循环,而是将循环中每次都需要重新计算但实际并不会发生改变的量用常量代替。本例中,这个量是向量v的长度。类似的还有使用strlen(s)求得的字符串长度。

  显然,如果这个量在每次循环时都会被改变,是不适用的。

复制代码
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;
    }
}
复制代码

 


 

版本3:减少过程调用

  也即减少函数调用。通过对汇编代码的学习,可以知道函数调用是需要一些额外的开销的,包括建栈、传参,以及返回。通过把访问元素的函数直接扩展到代码中可以避免这个开销。但是这样做牺牲了代码的模块性和抽象性,对这种改变编写文档是一种折衷的补救措施。

  在这里,是通过把get_vec_element()展开来达到这个目的的,这需要编程人员对这个数据结构的细节有所了解,增加了阅读和改进代码的难度。

  更通用地方法是使用内联函数inline,可以兼顾模块性和抽象性。

复制代码
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];
    }
}
复制代码

 


 

版本4:消除不必要的存储器引用

  在对于vec_rec结构操作时,其中间结果*dest是存放在存储器中的,每次取取值和更新都需要对存储器进行load或store, 要慢于对寄存器的操作。如果使用寄存器来保存中间结果,可以减少这个开销。

  这个中间结果,可以通过register显式声明为寄存器变量。不过下面的代码经过汇编后可以发现acc是存放在寄存器中的,不必显示地声明。

  然而,中间变量并非越多越好。原书5.11.1节展示了一个情形,如果同时使用的中间变量数过多,会出现“寄存器溢出”现象,部分中间变量仍然需要通过存储器保存。同理,多于机器支持能力的register声明的变量并不一定全部使用了寄存器来保存。

复制代码
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;
}
复制代码

 


版本5:循环展开

  这个优化措施利用了对CPU数据流的知识,比汇编代码更接近机器底层。简单地说是利用了CPU的并行性,将数据分成不相关的部分并行地处理。版本5~7的更多细节和原理可以参考原书。类似的原理在练习题5.5和5.6中展示了为什么Horner法比一般的多项式求值的运算次数少,反而更慢的原因。

  展开的次数可以根据情况而定,下面的代码只展开了两次。对于未处理的部分元素,不能遗漏。

  gcc可以通过-funroll-loops选项执行循环展开。

复制代码
void combine5(vec_ptr v, data_t *dest)
{
    long int i;
    long int length = vec_length(v);
    long int limit = length-1;
    data_t *data = get_vec_start(v);
    data_t acc = IDENT;
    /* Combine 2 elements at a time */
    for (i = 0; i < limit; i+=2) {
        acc = (acc OP data[i]) OP data[i+1];
    }

    /* Finish any remaining elements */
    for (; i < length; i++) {
      acc = acc OP data[i];
    }
    *dest = acc;
}
复制代码

 


 

版本6与版本7:提高并行性

  和版本5的思想类似,但由于并行化更高,性能更好一些,充分利用了向量中各个元素的不相关性。

  版本6使用多个累积变量方法。

复制代码
/* Unroll loop by 2, 2-way parallelism */
void combine6(vec_ptr v, data_t *dest)
{
    long int i;
    long int length = vec_length(v);
    long int limit = length-1;
    data_t *data = get_vec_start(v);
    data_t acc0 = IDENT;
    data_t acc1 = IDENT;

    /* Combine 2 elements at a time */
    for (i = 0; i < limit; i+=2) {
    acc0 = acc0 OP data[i];
    acc1 = acc1 OP data[i+1];
    }
    /* Finish any remaining elements */
    for (; i < length; i++) {
    acc0 = acc0 OP data[i];
    }

*dest = acc0 OP acc1;
}
复制代码

 

  版本7是在版本5的基础上打破顺序相关,改变了并行执行的操作数量。

复制代码
void combine7(vec_ptr v, data_t *dest)
{
    long int i;
    long int length = vec_length(v);
    long int limit = length-1;
    data_t *data = get_vec_start(v);
    data_t acc = IDENT;

    /* Combine 2 elements at a time */
    for (i = 0; i < limit; i+=2) {
        acc = acc OP (data[i] OP data[i+1]);
    //in combine5:
//acc =
( acc OP data[i]) OP data[i+1];
    }

    /* Finish any remaining elements */
    for (; i < length; i++) {
        acc = acc OP data[i];
    }
    *dest = acc;
}
复制代码

 


补充说明

  这个示例中没有提到的改进方法还有:书写适合条件传送实现的代码,下面是原书的两段用于对比的代码,后者更适合条件传送实现。

复制代码
void minmax1(int a[], int b[], int n) {
    int i;
    for(i=0;i<n; i++) {
        if (a[i] > b[i]) {
            int t = a[i];
            a[i] = b[i];
            b[i] = t;
        }
    }
}

/* Rearrange two vectors so that for each i, b[i] >= a[i] */
void minmax2(int a[], int b[], int n) {
    int i;
    for(i=0;i<n; i++) {
        int min = a[i] < b[i] ? a[i] : b[i];
        int max = a[i] < b[i] ? b[i] : a[i];
        a[i] = min;
        b[i] = max;
    }
}
复制代码

 

 

 

 





本文转自五岳博客园博客,原文链接:http://www.cnblogs.com/wuyuegb2312/p/3656183.html,如需转载请自行联系原作者

目录
相关文章
|
3月前
|
存储 缓存 监控
|
2月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
54 6
|
2月前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
55 3
|
7月前
|
机器学习/深度学习 算法 安全
探索现代操作系统的内核设计与优化
在当今数字化时代,操作系统的内核是计算机系统稳定、高效运行的关键。本文深入探讨了现代操作系统内核的设计原则和优化方法,从微内核到宏内核,详细分析了它们各自的优缺点,并探讨了未来内核的发展趋势和创新方向。
103 1
|
3月前
|
安全 调度 虚拟化
探索现代操作系统的架构与优化
本文将深入探讨现代操作系统的核心架构和优化技术。从操作系统的基本定义入手,逐步解析其内核结构、进程管理、内存管理和I/O系统。同时,还将讨论现代操作系统在多核处理器支持、虚拟化技术和安全性方面的创新与优化措施。通过这些内容,读者可以全面了解操作系统的工作原理及其在实际应用中的表现与改进。
|
5月前
|
存储 编译器 数据处理
汇编语言在嵌入式系统开发中的核心作用:深入硬件控制,优化系统性能的关键技术。
【8月更文挑战第31天】尽管高级编程语言已广泛应用于软件开发,但在嵌入式系统领域,汇编语言仍不可或缺。它直接操作硬件,实现对中断处理、定时器配置及DMA传输的精准控制,提升系统性能。通过具体示例,如ARM架构下的中断服务程序,展示了汇编语言的独特优势。掌握汇编语言对于嵌入式开发者至关重要,有助于深入理解硬件并优化系统效率。
108 0
|
6月前
|
缓存 算法 Java
探索现代操作系统中的内存管理优化策略
【7月更文挑战第24天】本文深入探讨了现代操作系统中内存管理的高级技术与优化策略。通过分析内存分配、虚拟内存机制以及缓存策略,文章揭示了如何提升系统性能和资源利用率。针对操作系统开发者和高级用户,本文提供了实用的调优技巧和未来的发展方向。
|
7月前
|
安全 Java 调度
Java并发编程:优化多线程应用的性能与安全性
在当今软件开发中,多线程编程已成为不可或缺的一部分,尤其在Java应用程序中更是如此。本文探讨了Java中多线程编程的关键挑战和解决方案,重点介绍了如何通过合理的并发控制和优化策略来提升应用程序的性能和安全性,以及避免常见的并发问题。
74 1
|
8月前
|
缓存 算法 Java
Java内存管理:优化性能和避免内存泄漏的关键技巧
综上所述,通过合适的数据结构选择、资源释放、对象复用、引用管理等技巧,可以优化Java程序的性能并避免内存泄漏问题。
121 5
|
8月前
|
存储 算法 C++
程序性能分析
1. 什么是程序性能 程序性能指的是程序在执行过程中所消耗的时间和资源的多少。一个好的程序应该能够在较短的时间内完成所需的任务,并且尽可能地利用少量的资源。
82 3

热门文章

最新文章

相关实验场景

更多
下一篇
开通oss服务