首发于lua in-depth

Lua5.4新特性

Lua5.4已经发布正式版。这个版本在语言层面上修改的东西并不多,但是默认的GC被换成了“分代式GC”,这对于那些经常产生短期对象的程序应该会有很明显的性能提升。GC带来的负担永远是自动内存管理语言的一大痛点,如果能在这一点上取得突破,那肯定比提供更多语法糖来得有价值。

此外5.4可以指定局部变量的属性,用这样的语法:

local a <NAME> = 3

NAME可以是constclose,为const时表示const变量(const variables),const变量可以帮助编译器作一些优化,比如下面的代码:

local a <const> = 4
local b = a + 7
print(b)

编译器会把a消除掉,直接给b赋11。这种优化是有限的,对于基本类型和字符串,能够有效减少寄存器的访问,但对于table貌似益处不大。代码文件如果需要一些数值常量,可以写成const变量,比如:

local MAX_LEN <const> = 20
function check_name(name)
    return #name <= MAX_LEN
end

check_name中就没有upvalue的访问,而是直接转换成和20的比较。

close变量(To-be-closed Variables)需要和close元方法结合使用,在变量超出作用域时,会调用变量的close元方法,这听起来是不是有点像C++的RAII用法。下面是一个例子:

local function newlock()
    local lock = {
        acquire = function()
            print("acquire lock")
        end,
        release = function()
            print("release lock")
        end,
    }
    return lock
end

local function lockguard(lock)
    local wrap = {
        lock = lock
    }
    lock.acquire()
    return setmetatable(wrap, {__close = function(t, err)
        t.lock.release()
    end})
end

local lock = newlock()
do
    for i = 1, 3 do
        local l <close> = lockguard(lock)
        print(i)
        error("err")
    end
end

定义local l <close>后,无论是否有错误,release都能得到调用;从这个例子也可以看出,close变量一般用于需要及时释放资源的情况;否则Lua的GC可以应付大多数情况。

除了上面提到的特性,还有一些新的修改如下:

  • userdata现在可以关联多个user值,C的API也有相应的修改,如果我们新建的userdata没有关联值,则尽量使用lua_newuserdatauv,这样更高效,lua_newuserdata仅仅为了兼容,且默认会关联1个值。
  • math.random使用了新的算法(基于xoshiro256**);并且会从随机的种子开始,使程序启动后第1次调用math.random会得到不同值。
  • 协程库提供了新的APIcoroutine.closelua_resetthreadcoroutine.close只能在挂起或死亡状态下调用,挂起状态下会使用协程进入死亡状态,并且关闭所有的close变量。

当然这些是明面上的修改,其实内部现实作了大量的优化,使得这个版本的性能比之前提高了40%左右,下面是我在阅读代码中的一些发现:

  • table的哈希node占用内存变小了,只需要24个字节;以前版本需要32个字节,这得使Lua的内存占用显著的减少。
  • table优化了数组部分的实现,使得#t的效率提高了数倍之多。
  • 函数原型的debug信息,优化了指令到代码行映射的内存占用,小的函数可以减少差不多3倍的内存。
  • luaV_execute在GNU下使用GCC的扩展(Labels as Values)实现更快的指令分派,从而优化VM的执行效率。
  • 虚拟机指令更加细化,这些细化都是为了提高VM的操作效率,比如加载指令增加了立即数的类型,这样就不必将立即数保存为常量,而是直接在指令中取就可以。具体的性能测试见这个文章
编辑于 2021-06-24 09:57