听到"高度优化的代码"或某些开发人员需要优化他们和诸如此类的东西,这是常见的.然而,作为一个自学成才的新程序员,我从来没有真正理解人们在谈论这些事情时究竟是什么意思.
关心一般的想法呢?此外,推荐一些阅读材料,无论你想在这件事上说什么.随意咆哮和讲道.
优化是我们懒惰地用来表示"以某种方式做出更好的事情"的术语.我们很少"优化"某些东西 - 更多,我们只是改进它,直到它满足我们的期望.
优化是我们为优化程序的某些部分而做出的改变.一个完全优化的程序通常意味着开发者抛出可读性窗外,并已重新编码算法以非明显的方式,以尽量减少"挂钟时间".(并不要求"优化代码"难以阅读,这只是一种趋势.)
可以优化:
内存消耗 - 使程序或算法的运行时大小更小.
CPU消耗 - 使算法在计算上不那么密集.
壁垒时间 - 尽一切可能使事情变得更快
可读性 - 您可以让人们更轻松地阅读它,而不是让您的应用程序更好地适用于计算机.
一些用于优化代码的常见(和过于通用)技术包括:
更改算法以改善性能特征.如果您的算法需要O(n ^ 2)时间或空间,请尝试将该算法替换为采用O(n*log n)的算法.
要减轻内存消耗,请查看代码并查找浪费的内存.例如,如果您有一个字符串密集型应用程序,则可以切换到使用子字符串引用(其中引用包含指向字符串的指针,以及用于定义其边界的索引),而不是从原始字符串分配和复制内存.
要减轻CPU消耗,请尽可能多地缓存中间结果.例如,如果需要计算一组数据的标准偏差,则每次需要知道std dev时,保存该单个数值结果而不是循环遍历该集合.
我大多没有实际的建议.
首先测量.应该对重要的地方进行优化.高度优化的代码通常难以维护并且是问题的根源.在代码不会减慢执行速度的地方,我更喜欢可维护性优化.熟悉分析,包括侵入式(仪表化)和非侵入式(低开销统计).学习阅读配置文件堆栈,了解时间包含/时间独占花费,为什么某些模式出现以及如何识别故障点.
你无法修复你无法衡量的东西.让您的程序通过一些性能基础架构报告其执行的操作和所需的时间.我来自Win32背景,所以我习惯了性能计数器,我非常慷慨地将它们全部洒在我的代码上.我甚至自动化了代码来生成它们.
最后是关于优化的一些话.关于优化的大多数讨论我都看到了关注任何编译器将为您免费优化的内容.根据我的经验,"高度优化的代码"的最大收益来源完全在别处:内存访问.在现代架构中,CPU大多数时间处于空闲状态,等待内存被提供给其管道.在L1和L2缓存未命中,TLB未命中,NUMA跨节点访问以及甚至必须从磁盘获取页面的GPF之间,现代应用程序的内存访问模式是可以进行的最重要的单一优化.我稍微夸张一点,当然会有反例的工作负载,这对于这种技术不利于内存访问局部性.但大多数应用都会.具体而言,这些技术的含义很简单:将数据集中在内存中,以便单个CPU可以在包含所有需要的紧凑内存范围内工作,无需在缓存行或当前页面外部进行昂贵的内存引用.在实践中,这可能意味着像按行而不是按列访问数组一样简单.
我建议你阅读1995年VLDB会议上提出的Alpha-Sort论文.本文介绍了专门针对现代CPU架构设计的高速缓存敏感算法如何能够从以前的基准测试中脱颖而出:
我们认为现代架构需要算法设计者重新检查它们对内存层次结构的使用.AlphaSort使用集群数据结构来获得良好的缓存局部性......
一般的想法是,当你在编译阶段创建源代码树时,在通过解析生成代码之前,你做了一个额外的步骤(优化),根据某些启发式方法,你将分支折叠在一起,删除不是的分支为多次使用的临时变量使用或添加额外节点.
想想像这段代码的东西:
a=(b+c)*3-(b+c)
哪个被翻译成
- * + + 3 b c b c
对于解析器来说,显然具有2个后代的+节点是相同的,因此它们将合并为临时变量t,并且树将被重写:
- * t t 3
现在一个更好的解析器会看到,因为t是一个整数,树可以进一步简化为:
* t 2
并且您最终将运行代码生成步骤的中间代码
int t=b+c; a=t*2;
将t标记为寄存器变量,这正是为汇编编写的.
最后要注意的是:您可以优化的不仅仅是运行时速度.您还可以优化内存消耗,这是相反的.展开循环和创建临时副本有助于加快代码的速度,他们也会使用更多的内存,所以这取决于你的目标是什么.