Lua性能优化指南

说明

本文主要取材于Lua Programming Gems一书的第二章Lua Performance Tips, 原书试读章节可点击这里下载

测试代码的运行环境均为Raspberry Pi 3, Lua 5.1.5

性能优化的基本原则

  1. 能不优化则不优化
  2. 先量化再优化:高手和菜鸟之间的区别不在于高手对于需要优化的点直觉更准,而是高手更清楚自己的经验和感觉都是不可靠的,只能依靠测试数据来定位性能瓶颈。

多用局部变量

就表达式 a = a + b而言,当a, b都是局部变量时,Lua虚拟机只需要执行1条指令就能完成这个加法操作;而当a, b都是全局变量时,则需要执行4条指令才能完成。

先看一个很明显使用了全局变量的例子

-- global_a.lua
a = 1
b = 1
for i = 1, 1000000 do
    a = a + b
end
-- local_a.lua
local a = 1
local b = 1
for i = 1, 1000000 do
    a = a + b
end

对比两段代码的运行时间, 使用局部变量的版本耗时仅为全局变量的30%

$ time lua global_a.lua
real    0m0.287s

$ time lua local_a.lua
real    0m0.093s

再看一个不那么明显的例子,对比下面两段代码的运行时间

-- global_sin.lua
for i = 1, 1000000 do
    local x = math.sin(i)
end
-- local_sin.lua
local sin = math.sin
for i = 1, 1000000 do
    local x = sin(i)
end

使用局部变量的版本耗时约为全局变量的70%

$ time lua global_sin.lua
real    0m0.663s

$ time lua local_sin.lua
real    0m0.509s

少用动态代码

由于编译代码是一件很消耗CPU的事情,出于性能的考虑,应该尽量使用静态代码,少用动态代码。

对比下面两段代码的运行时间

-- dynamic_code.lua
local lim = 10000
local a = {}
for i = 1, lim do
    a[i] = loadstring(string.format("return %d", i))
end
print(a[10]())  --> 10
-- static_code.lua
local lim = 10000
local a = {}
local fk = function(k) return function() return k end end
for i = 1, lim do
    a[i] = fk(i)
end
print(a[10]())  --> 10

使用静态代码的版本耗时仅仅是动态代码的10%

$ time lua dynamic_code.lua
10
real    0m0.219s

$ time lua static_code.lua
10
real    0m0.024s

定义小表时指定初始元素

表是Lua中唯一的数据结构,它的实现分为两部分: 数组部分和散列部分。数组部分用于存放从1开始的连续整数key,散列部分用于存放和已有数组索引不连续的整数以及其他类型的key。例如, t = {100, 200, 300, x = 9.2, pi = 3.14} 的数据实现如下图: [1]

为了尽可能节省内存,Lua虚拟机在定义一个空表时不会为表内元素预先分配空间。当插入新元素由于表的容量不足而需要扩容时,Lua虚拟机会将表的容量增长到不小于元素个数的2的幂。散列部分的扩容操作涉及下面两个动作:

  1. 为bucket数组分配一个更大的空间
  2. 重新散列表内元素

因此,当我们定义一个空表T并往里面插入3个字符串元素时,T的散列部分的bucket数组会经历先从0(空表)到1(2的0次幂),再从1到2(2的1次幂),最后从2到4(2的2次幂)一共三次的扩容过程。但是,如果在定义这个大小为3的表时指定了3个初始元素,Lua虚拟机就会一次性为这个表的bucket数组分配4的容量,并消除三次扩容中重新散列的的计算过程,从而实现更优的性能。

对比下面两段代码的运行时间

-- empty_table.lua
for i = 1, 1000000 do
    local t = {}
    t['e'] = 1; t['s'] = 2; t['m'] = 3
end
-- known_table.lua
for i = 1, 1000000 do
    local t = {e = 1, s = 2, m = 3}
end

指定了初始元素的版本耗时不到空表版本的50%

$ time lua empty_table.lua
real    0m4.290s

$ time lua known_table.lua
real    0m1.962s

以上对比了只使用了散列部分的表的两种初始化方式的性能,我们再来看只使用了数组部分的表的情况。数组部分的扩容操作和散列部分的bucket数组的扩容操作是一样的。只使用数组部分的表的扩容虽不涉及重新散列操作,但仍需要重新分配内存和复制已有元素,因此预分配的实现依然更为高效。

对比下面两段代码的运行时间

-- empty_array.lua
for i = 1, 1000000 do
    local t = {}
    t[1] = 'e'; t[2] = 's'; t[3] = 'm'
end
-- known_array.lua
for i = 1, 1000000 do
    local t = {'e', 's', 'm'}
end

指定了初始元素的版本耗时不到空表版本的50%

$ time lua empty_array.lua
real    0m0.870s

$ time lua known_array.lua
real    0m0.340s

使用table.concat函数实现多个字符串的拼接操作

Lua对于相同的字符串只保留一份拷贝,所有变量均是指向字符串的引用。这样设计的优点除了能节省内存,还能在常数时间内实现两个任意长度字符串的比较操作。但弱点在于进行两个字符串的拼接操作时必须先将前面那个字符串复制一个副本出来,再将后面那个字符串添加到该副本后面。因此,如果直接使用“..”运算符来实现字符串拼接,时间复杂度关于拼接字符串的个数不是线性的,而是成平方关系。为了解决这个问题,Lua引入了能使字符串拼接操作保持线性时间复杂度的标准库函数table.concat。

对比下面两段代码的运行时间

-- direct_append.lua
r = ""
s = "0123456789abcdefghijklmnopqrstuvwxyz"
for i = 1, 10000 do
    r = r .. s
end
-- table_concat.lua
s = "0123456789abcdefghijklmnopqrstuvwxyz"
t = {}
for i = 1, 10000 do
    t[i] = s
end
table.concat(t)

使用table.concat的版本耗时不到直接拼接的1%

$ time lua direct_append.lua
real    0m2.924s
$ time lua table_concat.lua
real    0m0.026s


[1] R. Ierusalimschy, L. H. Figueiredo, and W. Celes. The Implementation of Lua 5.0, pages 6-8, https://www.lua.org/doc/jucs05.pdf.

时间: 2025-01-21 11:46:59

Lua性能优化指南的相关文章

jQuery性能优化指南

jQuery性能优化指南,可以从以下12个方向考虑. 1,总是从ID选择器开始继承 2,在class前使用tag(标签名) 3,将jQuery对象缓存起来(在多次使用是,用一个中间变量代替,而不是总是用选择器)   4,对直接的DOM操作进行限制 5,注意尽量减少事件冒泡 6,推迟到 $(window).load   7,压缩JavaScript 8,尽量使用ID代替Class. 9,给选择器一个上下文   10,慎用 .live()方法(应该说尽量不要使用) 11,子选择器和后代选择器   1

Web 开发者的 HTTP/2 性能优化指南

本文讲的是Web 开发者的 HTTP/2 性能优化指南, HTTP/2改变了Web开发者优化网站的方式.在HTTP/1.1中,为了压缩5%的页面加载速度,人们会通过雪碧图.内联代码.细分域名.合并代码等方式,来想方设法地优化TCP连接和HTTP请求. HTTP/2带来了些许便利.一般网站无需复杂的构建和部署流程即可获得30%的性能提升.在这篇文章中,我们会讨论HTTP/2下网站优化的最佳实践. HTTP/1.1中的Web优化 HTTP/1.1中大多数的网站性能优化技术都是减少向服务器发起的HTT

Jquery 学习笔记(二)jQuery性能优化指南

Jquery 学习笔记(二) -jQuery性能优化指南 2009年11月30日 一 作者:   邦畿千里   1,总是从ID选择器开始继承 在jQuery中最快的选择器是ID选择器,因为它直接来自于JavaScript的getElementById()方法. 例如有一段HTML代码: <div id="content"> <form method="post" action="#"> <h2>交通信号灯<

jQuery 性能优化指南 (1)_jquery

1,总是从ID选择器开始继承 在jQuery中最快的选择器是ID选择器,因为它直接来自于JavaScript的getElementById()方法. 例如有一段HTML代码: 复制代码 代码如下: <div id="content"> <form method="post" action="#"> <h2>交通信号灯</h2> <ul id="traffic_light"&

jQuery 性能优化指南(3)_jquery

8,尽量使用ID代替Class.   前面性能优化已经说过,ID选择器的速度是最快的.所以在HTML代码中,能使用ID的尽量使用ID来代替class.看下面的一个例子: // 创建一个list var $myList = $('#myList'); var myListItems = '<ul>'; for (i = 0; i < 1000; i++) {      myListItems += '<li class="listItem' + i + '">

jQuery 性能优化指南(2)_jquery

4,对直接的DOM操作进行限制   这里的基本思想是在内存中建立你确实想要的东西,然后更新DOM . 这并不是一个jQuery最佳实践,但必须进行有效的JavaScript操作 .直接的DOM操作速度很慢. 例如,你想动态的创建一组列表元素,千万不要这样做,如下所示: var top_100_list = [...], // 假设这里是100个独一无二的字符串 $mylist = $("#mylist"); // jQuery 选择到 <ul> 元素 for (var i=

移动HTML 5前端性能优化指南

  前端工程师的菜!最近移动Html 5越来越火,想有一个体验流畅的Html 5 应用,这篇优化指南就别放过咯.腾讯的同学将关键的注意点与优化方法都总结出来,全文高能干货,非常值得深度学习 >>> 概述 PC优化手段在Mobile侧同样适用 在Mobile侧我们提出三秒种渲染完成首屏指标 基于第二点,首屏加载3秒完成或使用Loading 基于联通3G网络平均338KB/s(2.71Mb/s),所以首屏资源不应超过1014KB Mobile侧因手机配置原因,除加载外渲染速度也是优化重点 基

IIS 网站服务器性能优化指南_win服务器

但配置.优化IIS的性能,使得网站访问性能达到最优状态却不是一件简单的事情,这里我就介绍一下如何一步一步的优化你的IIS服务器. 服务器端环境,我们以Windows Server 2003的IIS6.0为例,客户端环境为Mozilla Firefox 3.0,同时安装Yahoo的YSlow扩展. YSlow是Yahoo开发者团队发布的一款基于Firebug的插件.用于分析网页,并根据一些高性能网站的规则进行相应的评级打分,对于网页性能优化有很好的帮助作用,告诉你那些部分影响了你的网页速度,并告诉

Lua性能优化技巧(一):前言_Lua

和在所有其他编程语言中一样,在Lua中,我们依然应当遵循下述两条有关程序优化的箴言: 原则1:不要做优化. 原则2:暂时不要做优化(对专家而言). 这两条原则对于Lua编程来说尤其有意义,Lua正是因其性能而在脚本语言中鹤立鸡群. 当然,我们都知道性能是编程中要考量的一个重要因素,指数级时间复杂度的算法会被认为是棘手的问题,绝非偶然.如果计算结果来得太迟,它就是无用的结果.因此,每一个优秀的程序员都应该时刻平衡在优化代码时所花费的资源和执行代码时所节省的资源. 优秀的程序员对于代码优化要提出的第