在这个网站上已经有很多性能问题了,但是在我看来,几乎所有的问题都是非常具体的,而且相当狭窄。几乎所有人都重复了避免过早优化的建议。
我们假设:
代码已经正常工作了
所选择的算法对于问题的环境已经是最优的
对代码进行了测量,并隔离了有问题的例程
所有优化的尝试也将被衡量,以确保它们不会使事情变得更糟
我在这里寻找的是策略和技巧,在一个关键算法中,当没有其他事情可做,但无论如何都要挤出最后百分之几。
理想情况下,尽量让答案与语言无关,并在适用的情况下指出所建议的策略的任何缺点。
我将添加一个带有我自己最初建议的回复,并期待Stack Overflow社区能想到的任何其他东西。
目前最重要的限制因素是有限的内存带宽。多核只会让情况变得更糟,因为带宽是在核之间共享的。此外,用于实现缓存的有限芯片区域也分配给了内核和线程,这进一步恶化了这个问题。最后,保持不同缓存一致性所需的芯片间信号也会随着核数的增加而增加。这也增加了一个惩罚。
这些是您需要管理的影响。有时是通过对代码的微观管理,但有时是通过仔细考虑和重构。
很多注释已经提到了缓存友好的代码。至少有两种不同的风格:
避免内存读取延迟。
降低内存总线压力(带宽)。
第一个问题与如何使数据访问模式更规则有关,从而使硬件预取器更有效地工作。避免动态内存分配,这会将数据对象分散在内存中。使用线性容器代替链表、散列和树。
第二个问题与提高数据重用有关。修改算法以处理适合可用缓存的数据子集,并在数据仍在缓存中时尽可能多地重用这些数据。
更紧密地封装数据并确保在热循环中使用缓存线路中的所有数据,将有助于避免这些其他影响,并允许在缓存中安装更多有用的数据。
我想这已经用不同的方式说过了。但是当你在处理一个处理器密集型算法时,你应该以牺牲其他所有东西为代价来简化最内部循环中的所有东西。
That may seem obvious to some, but it's something I try to focus on regardless of the language I'm working with. If you're dealing with nested loops, for example, and you find an opportunity to take some code down a level, you can in some cases drastically speed up your code. As another example, there are the little things to think about like working with integers instead of floating point variables whenever you can, and using multiplication instead of division whenever you can. Again, these are things that should be considered for your most inner loop.
有时,您可能会发现在内循环中对整数执行数学运算的好处,然后将其缩小为随后可以使用的浮点变量。这是一个牺牲一个部分的速度来提高另一个部分的速度的例子,但在某些情况下,这样做是值得的。
更多的建议:
Avoid I/O: Any I/O (disk, network, ports, etc.) is
always going to be far slower than any code that is
performing calculations, so get rid of any I/O that you do
not strictly need.
Move I/O up-front: Load up all the data you are going
to need for a calculation up-front, so that you do not
have repeated I/O waits within the core of a critical
algorithm (and maybe as a result repeated disk seeks, when
loading all the data in one hit may avoid seeking).
Delay I/O: Do not write out your results until the
calculation is over, store them in a data structure and
then dump that out in one go at the end when the hard work
is done.
Threaded I/O: For those daring enough, combine 'I/O
up-front' or 'Delay I/O' with the actual calculation by
moving the loading into a parallel thread, so that while
you are loading more data you can work on a calculation on
the data you already have, or while you calculate the next
batch of data you can simultaneously write out the results
from the last batch.
添加这个答案,因为我没有看到它包括在所有其他。
最小化类型和符号之间的隐式转换:
这至少适用于C/ c++,即使你已经认为你已经摆脱了转换——有时测试在需要性能的函数周围添加编译器警告是很好的,特别是注意循环中的转换。
特定于GCC:您可以通过在代码周围添加一些冗长的pragmas来测试这一点,
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic error "-Wsign-conversion"
# pragma GCC diagnostic error "-Wdouble-promotion"
# pragma GCC diagnostic error "-Wsign-compare"
# pragma GCC diagnostic error "-Wconversion"
#endif
/* your code */
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
我曾见过一些案例,你可以通过减少这样的警告所带来的转化率来获得几个百分点的加速。
在某些情况下,我有一个带有严格警告的头,我保留了这些警告,以防止意外转换,然而这是一种权衡,因为您可能最终会为安静的故意转换添加大量强制转换,这可能会使代码更加混乱,而收益却微乎其微。