Lua性能优化技巧(一):前言
和在所有其他编程语言中一样,在Lua中,我们依然应当遵循下述两条有关程序优化的箴言:
原则1:不要做优化。
原则2:暂时不要做优化(对专家而言)。
这两条原则对于Lua编程来说尤其有意义,Lua正是因其性能而在脚本语言中鹤立鸡群。
当然,我们都知道性能是编程中要考量的一个重要因素,指数级时间复杂度的算法会被认为是棘手的问题,绝非偶然。如果计算结果来得太迟,它就是无用的结果。因此,每一个优秀的程序员都应该时刻平衡在优化代码时所花费的资源和执行代码时所节省的资源。
优秀的程序员对于代码优化要提出的第一个问题是:“这个程序需要被优化吗?”如果(仅当此时)答案是肯定的,第二个问题则是:“在哪里优化?”
要回答这样两个问题,我们需要制定一些标准。在进行有效的性能评定之前,不应该做任何优化工作。有经验的程序员和初学者之前的区别并非在于前者善于指出一个程序的主要性能开销所在,而是前者知道自己不善于做这件事情。
几年前,Noemi Rodriguez和我开发了一个用于Lua的CORBA ORB[2]原型,之后演变为OiL。作为第一个原型,我们的实现的目标是简洁。为防止对额外的C函数库的依赖,这个原型在序列化整数时使用少量四则运算来分离各个字节(转换为以256为底),且不支持浮点值。由于CORBA视字符串为字符序列,我们的ORB最初也将Lua字符串转换为一个字符序列(也就是一个Lua表),并且将其和其他序列等同视之。
当我们完成这个原型之后,我们把它的性能和一个使用C++实现的专业ORB进行对比。由于我们的ORB是使用Lua实现的,预期上我们可以容忍它的速度要慢一些,但是对比结果显示它慢得太多了,让我们非常失望。一开始,我们把责任归结于Lua本身;后来我们怀疑问题出在那些需要序列化整数的操作上。我们使用了一个非常简单的性能分析器(Profiler),与在《Lua程序设计》[3]第23章里描述的那个没什么太大差别。出乎我们意料的是,整数序列化并没有明显拖慢程序的速度,因为并没有太多整数需要序列化;反而是序列化字符串需要对低性能负很大责任。实际上,每一条CORBA消息都包含若干个字符串,即使我们没有显式地操作字符串亦是如此。而且序列化每一条字符串都是一个性能开销巨大的工作,因为它需要创建一个新表,并使用单独的字符填充;然后序列化整个序列,其中需要依次序列化每个字符。一旦我们将字符串序列化作为一种特殊情况(而不是通过通用的序列化流程)重新实现,整个程序的性能就得到了显著的提升。我们只是添加了几行代码,程序的性能已经和C++实现的那个版本有得一拼了[4]。
因此,我们总是应该在优化性能之前进行性能测试。通过测试,才能了解到要优化什么;在优化后再次测试,来确认我们的优化工作确实带来了性能的提升。
一旦你决定必须优化你的Lua代码,本文将可能有所帮助。本文描述了一些优化方式,主要是展示在Lua中怎么做会更慢,怎么做又会更快。在这里,我将不会讨论一些通用的优化技巧,例如优化算法等等——当然,你应该掌握和使用这些技巧,有很多其他地方可以了解这方面的内容。本文主要讨论一些专门针对Lua的优化技巧,与此同时,我还会持续地测试小程序的时间和空间性能。如果没有特别注明的话,所有的测试都在一台Pentium IV 2.9GHz、1GB内存、运行Ubuntu 7.10、Lua 5.1.1的机器上进行。我经常会给出实际的测量结果(例如7秒),但是这只在和其他测量数据进行对比时有意义。而当我说一个程序比另一个快X%时,意味着前者比后者少消耗X%的时间(也就是说,比另一个程序快100%的程序的运行不需要时间);当我说一个程序比另一个慢X%时,则是说后者比前者快X%(意即,比另一个程序慢50%的程序消耗的时间是前者的两倍)。