我知道你应该只在必要时优化事物.但是,如果认为有必要,你最喜欢的低级别(与算法级别相比)优化技巧是什么.
例如:循环展开.
gcc -O2
编译器可以做得更好.
为过滤器,循环缓冲器等选择2的幂.
非常非常方便.
-亚当
当然,为什么,有点笨拙的黑客!
科学代码中最有用的一个就是pow(x,4)
用x*x*x*x
.Pow几乎总是比乘法更昂贵.接下来是
for(int i = 0; i < N; i++) { z += x/y; }
至
double denom = 1/y; for(int i = 0; i < N; i++) { z += x*denom; }
但我最喜欢的低级优化是确定哪些计算可以从循环中删除.它总是更快地进行一次计算而不是N次.根据您的编译器,其中一些可能会自动为您完成.
检查编译器的输出,然后尝试强制它以更快地执行某些操作.
我不一定称之为低级优化,但通过明智地应用缓存,我通过所有低级别技巧的应用程序组合,已经节省了数量级更多的周期.其中许多方法都是特定于应用的.
具有数据库查询的LRU缓存(或任何其他基于IPC的请求).
记住上次失败的数据库查询,并在特定时间范围内重新请求时返回失败.
记住您在大型数据结构中的位置,以确保如果下一个请求是针对同一节点,则搜索是免费的.
缓存计算结果以防止重复工作.除了更复杂的情况,这常常发现if
或for
语句.
CPU和编译器不断变化.不管底层代码的技巧,是有道理的3个CPU芯片与前不同的编译器可能实际上是对当前建筑速度较慢,有可能是一个很好的机会,这一招可能会混淆谁是维持在未来这段代码.
++i
可以比快i++
,因为它避免创建一个临时的.
对于现代C/C++/Java/C#编译器,这是否仍然适用,我不知道.对于具有重载运算符的用户定义类型,它可能是不同的,而在简单整数的情况下,它可能无关紧要.
但我开始喜欢它的语法......它读起来像"增量i",这是一个合理的顺序.
使用模板元编程在编译时而不是在运行时计算事物.
多年以前,有一个不那么聪明的编译器,我从函数内联,行走指针而不是索引数组获得了很大的里程,并且迭代到零而不是最大值.
如果有疑问,对汇编的一点知识将让你看看编译器产生了什么并攻击低效的部分(使用源语言,使用对编译器更友好的结构.)
预先计算值.
例如,如果你的应用不一定需要角度非常精确,而不是sin(a)或cos(a),也许你用圆圈的1/256表示角度,并创建浮点数正弦[]和余弦[]预先计算这些角度的sin和cos.
并且,如果您经常需要一个给定长度的某个角度的矢量,您可能会预先计算所有那些已经乘以该长度的正弦和余弦.
或者,更普遍的说,交易记忆速度.
或者,更一般地说,"所有编程都是一种缓存练习" - Terje Mathisen
有些事情不太明显.例如,遍历二维数组,您可能会执行类似的操作
for (x=0;x如果您这样做,您可能会发现处理器缓存更喜欢它:
for (y=0;y或相反亦然.
11> alex strange..:不要循环展开.不要做Duff的设备.使您的循环尽可能小,其他任何东西都会抑制x86性能和gcc优化器性能.
摆脱分支可能是有用的,所以完全摆脱循环是好的,那些无分支的数学技巧确实有效.除此之外,尽量不要离开L2缓存 - 这意味着如果浪费缓存空间,也应该避免大量的预先计算/缓存.
并且,特别是对于x86,尝试在任何时候保持使用的变量数量.很难说这些编译器会用这种东西做什么,但通常用较少的循环迭代变量/数组索引最终会得到更好的asm输出.
当然,这适用于台式机CPU; 具有快速内存访问速度的慢CPU可以预先计算更多,但在这些日子里,这可能是一个总内存很少的嵌入式系统......