当前位置: 首页 > backend >正文

优化程序性能 | 《深入理解计算机系统》第五章笔记

前言

作为一个程序员,其编写的程序需要正确且高效。一个高效的程序具备以下两点:

  1. 对于需要解决的问题,程序选择了正确的算法与数据结构;
  2. 源代码可以被编译器有效优化为高效的可执行代码。

因此,需要学习如何对程序的性能进行优化。其本质是兼顾高级语言的可读性,同时考虑编译器的优化方式,在编译器的优化范围内,尽可能的提高程序的高效性。

编译器能力的局限

现代的编译器,虽然能够对源代码进行一定的优化,但是其仍然可能出现错误。所以一般来说,编译器仍然会以程序的安全性(优化后的可执行代码要与原始代码能够产生相同的结果)为主,然后才考虑程序的高效性。

void twiddle1(long *xp, long *yp) {*xp += *yp;*xp += *yp;
}void twiddle2(long *xp, long * yp) {*xp += 2 * *yp;
}

以上面两个函数为例,在一般情况下,调用两个函数后的结果是一致的,但是twiddle2在性能上略有优势。不过编译器在遇到twiddle1时,并不会直接优化得到twiddle2这个函数,因为当指向的地址是一致的,那么两个函数最终得到的结果并不相同。编译器在编译的时候无法预知xp,yp指向的具体地址,不能盲目的进行程序的优化,否则会得到不一样的运行结果。
编译器也不会判断函数调用的时候会不会产生副作用(见书P344,比如函数内尝试修改全局函数),所以一般情况下,它会假设最糟糕的情况,并保持所有的函数调用不变。

优化程序性能的具体方法

程序性能的评价指标

每元素的周期数(Cycles Per Element, CPE).简单说就是处理一个元素(比如一行代码)经历的CPU时钟的周期数,该指标越小,程序的性能越优。
CPE一定是一个整数,但是在实验中是经过拟合得到的结果,所以不可避免的会产生小数

消除循环的低效率

void for01(vector<int>>& nums) {for(int i = 0; i < nums.size(); i++) {...}
} void for02(vector<int>>& nums) {int n = nums.size();for(int i = 0; i < n; i++) {...}
}

由于每次循环迭代时都会对测试条件求值,如果采用for01的方式,则每次都要对向量的长度进行计算。而向量的长度是不变的,所以可以优化为for02的代码,这样就只用计算一次向量的长度。

这样的代码优化的方法被称为代码移动(code motion)。对于循环中将要执行多次但是计算结果不会改变的计算,可以将计算移动到循环前面,则不会多次求重复值。

上述优化操作可以避免渐进低效率的发生,比如一下两个函数。lower1的时间复杂度本质是O(n2)O(n^2)O(n2),lower2的时间复杂度是O(n)O(n)O(n).如果字符串长度较短,则难以察觉二者的性能差异。

void lower1(char* s) {long i;for(i = 0; i < strlen(s); i++)if(s[i] >= 'A' && s[i] <= 'Z')s[i] -= ('A' - 'a')
}void lower2(char *s) {long i;long len = strlen(s);for(i = 0; i < len; i++)if(s[i] >= 'A' && s[i] <= 'Z')s[i] -= ('A' - 'a')
}

减少过程调用

该优化方法指直接访问底层数据结构,比如

  1. 直接通过数组int*存储数据,而不是vector<int>
  2. 使用容器,如vector时,在已知数据量的时候,提前申请足够的内存空间,避免反复扩容。

消除不必要的内存引用

尽量避免反复从内存读取数值,可以引入在循环外声明的临时变量,来消除不必要的内存读写。
sum1相比sum2,每次循环会多一次从内存的读,多一次向内存的写。(获得res处的数据,更新res的值)。sum2使用临时变量,规避了上述问题,因为tmp一直存储于寄存器中,并不需要向内存进行读写操作。

void sum1(int* v, int length, int* res) {for(int i = 0; i < length; i++) {*res = *res + v[i];}
}void sum2(int* v, int length, int* res) {int tmp = 0;for(int i = 0; i < length; i++) {tmp = tmp + v[i];}*res = tmp;
}
http://www.xdnf.cn/news/19815.html

相关文章:

  • React实现列表拖拽排序
  • LiteFlow:国产流程编排引擎体验
  • DAY20-新世纪DL(DeepLearning/深度学习)战士:终(目标检测/YOLO)3
  • 【医疗行业案例】基于 React 的预约系统:DHTMLX 助力高效排班与预约管理
  • CAD/BIM软件产品技术深度分析文章写作计划
  • 全渠道 + 低代码:如何打造 “内外协同” 的客服管理系统体系?
  • 【FastDDS】Layer DDS之Domain ( 02-DomainParticipant )
  • unity中的交互控制脚本
  • 云手机将要面临的挑战有哪些?
  • 【学习记录】github私人仓库创建和本地克隆
  • CSS 伪类与伪元素:深度解析
  • 从零构建Linux Shell解释器深入理解Bash进程创建机制
  • 【Spring Cloud微服务】11.微服务通信演义:从飞鸽传书到5G全息,一部消息中间件的进化史诗
  • Java项目打包成EXE全攻略​
  • Ubuntu22.04下编译googletest源代码生成.so动态库
  • 利用 openssl api 实现 TLS 双向认证
  • MySQL-MVCC多版本并发控制详解
  • LangChain实战(十二):自定义Tools扩展Agent能力
  • Python+DRVT 从外部调用 Revit:批量创建门
  • Streamable HTTP
  • sv中forever如何结束
  • AI 在金融、医疗、教育、制造业等领域有着广泛的应用,以下是这些领域的一些落地案例
  • STM32HAL 快速入门(十七):UART 硬件结构 —— 从寄存器到数据收发流程
  • 告别剪辑烦恼!3个超实用技巧,让你的视频瞬间高级起来
  • 【音视频】视频秒播优化实践
  • UnityWebRequest 数据获取和提交
  • wpf 只能输入int类型的文本框
  • WebSocket客户端库:websocket-fruge365
  • Ubuntu下把 SD 卡格式化为 FAT32
  • Hostol Magento电商服务器套餐:基于阿里云,预配置高性能环境,一键开店