性能优化是个手艺活

大数据的技术本质就是高性能,性能优化也是程序员们的永恒话题。

这里说的性能优化,主要是指在程序员的努力下能达到某种性能提升效果的过程。只要简单换台机器就能加速的事情,业主方要么早就做过了,要么就是条件不允许这么做。

这时候,优化方案的关键在于算法,具体来讲,就是要设计出低复杂度的计算方案。我们说过多次,软件不可能提高硬件的性能,只有采用了低复杂度的算法,也就是计算规模有了实质性的下降时,才可能在相同硬件环境下获得更优的性能。

不幸的是,这类方案通常都是定制化的,换一个场景可能就没有效果了。换句话说,这些方案是专门为某个计算任务设计的。


以分组运算举例:如果我们事先知道 GROUP BY 的结果集很小(可以在内存中放下),那么单次遍历不需要生成临时外存数据就可以完成运算,而且也容易采用并行计算。而如果结果集很大、内存无法装下时,则需要生成临时缓存数据用于保持中间结果,而这时候也几乎不可能再用并行机制,因为外存的并发冲突经常会抵销掉并行带来的好处。而如果我们知道用于分组的数据已经针对分组字段有序时,则即使是大结果集也可以不必生成临时缓存数据,这样又可以采取并行计算的手段来提高性能。反过来,如果我们在大结果集时使用了小结果集的算法,那就会导致内存溢出;而在无序情况下使用了有序算法,则会得出错误的计算结果。

也就是说,看起来非常类似的计算任务,在不同场景下会采用很不一样的动作,这需要了解计算和数据特征才能做出选择。

如果要考虑通用的场景,那只能使用最保守的算法。对于分组运算来讲,也就是使用大结果集且数据无序的算法,这样虽然能保证正确计算出结果,但性能就会很差。


这样,性能优化就是个手艺活了!

手艺活需要就事论事地精雕细刻,这又需要我们手里有个好工具,能够让我们快速实现设计出来的计算方案,不能发生想出好算法却无法实现的迥境。

作为当前数据计算主流的传统关系数据库在这方面做得很差。SQL 提供的运算太少,而且透明度过高,程序员对计算过程的控制力度很弱,很难实现自己的想法。比如上面说过几种在某些场景下可以提升性能的分组算法,SQL 都没有直接在语法层面上提供,只能指望数据库引擎的自动优化,而这又严重依赖于数据库系统以及当前场景的复杂性,这就经常会发生我们明知有好算法却无能为力的现象。

事实上,SQL 并不是完全没有提供这种能力。有经验的程序员在发现 SQL 执行过慢时都会去观察数据库采用的执行计划,然后再去调整 SQL 语句让数据库采用自己期望的路径去计算。有些数据库也提供了一些关键字来人为改变执行计划,从而达到控制计算过程的目标以期获得更优性能。但无论如何,数据库在这方面提供的功能仍然非常粗糙,远远满足不了程序员进行性能优化的需求。

使用集算器的 SPL 编程能实现更高性能的原因就在于此。SPL 中提供了大量仅适合某个场景的运算方法,程序员就可以根据运算和数据的特征选择合适的方法来组合出低复杂度的算法。这时候,即使单项运算的性能并不比数据库更高(用 Java 写的集算器在基本运算方面一般不会比用 C++ 写的数据库性能更好),但仍然经常能获得数量级的性能提升,这就是算法的力量。


总结一下,性能优化的关键在于两条:1. 能根据任务特征设计出更优的算法;2. 有好的工具能迅速实现这个算法。想不出好算法时,有再好的工具也是白搭;而想出好办法了,没有好工具也只能干瞪眼。

设计算法虽然没有固定的程序,但还是有一些常规套路,比如前面举例说的分组运算的不同情况应当采用的手段。这需要充分了解运算原理,后续我会就这个话题写一系列文章,介绍各种场景下可能采用的低复杂度算法及其原理。初步列了一下提纲,这个系列话题估计能写三五十篇文章出来。