skynet项目内存使用优化

Skynet/Lua 凭借其轻量易用、灵活热更、Actor并发模型的特性被广泛应用于游戏开发等场景,但 Lua 动态内存管理机制也带来了内存占用过高的挑战。

本文结合我最近对一个 skynet 项目做的内存优化分析经验,做一下分享。

1、lua 内存使用优化

1.1. 减少 lua 函数调用层数

服务端收到协议后,要经过很多次函数调用才到真正的执行函数。以手头的项目举例,服务 A 函数负责接收协议数据,收到数据后,会将协议和参数传给 B 函数处理,B 函数根据定义找到对应的模块和方法,再由 C 函数排队(一个玩家一个队列)调用 D 目录下对应的模块和函数, D 目录的模块再 require 对应的目标模块,才调用到真正的执行函数 E。最后,E 函数还会调用其他模块来完成业务逻辑。

调用层数过深的问题是,lua 要为每个过程保留过程数据,如函数地址、参数等,也就是调用栈信息(CallInfo),也叫栈帧

建议:减少调用层级,可以使用注册回调机制
继续阅读skynet项目内存使用优化

lua table # 取长度问题

任何语言都不是完美无瑕的,在使用中都有各种问题,lua 也不例外。而 lua 使用中,绝对绕不开的一个问题就是 # 取 table 长度问题。

问题描述

先看下 # 的一些使用情况,希望引起你的困惑:

> t = {1,1,nil,1}
> #t
4
> t = {nil,nil,1,nil}
> #t
0
> t = {1,nil,nil,nil,nil,nil,nil,1}
> #t
8
> t[9] = 1
> #t
1
> t = {1,nil,nil,nil,nil,nil,nil,1,1}
> #t
9

当 table 有部分值为 nil 时,你很难清楚 # 取得的结果是什么。(如果你想知道 table 实际长度,只能遍历 table)
继续阅读lua table # 取长度问题

lua定位CPU100%问题

在开始这个话题前,说下我遇到的 skynet 进程 CPU100% 占用问题,查找 bug 的过程比较繁琐,后来想到做改进,这也是促成我写这篇文章的原因。

查到是一段 lua 代码有问题,这里截取其中出问题的代码:

-- 活动开始时间: 2033-01-01 00:00:00
local start_time = 1988121600

function start_activity()
    local now = os.time()
    local diff = now - start_time
    if diff < 0 then
        skynet.timeout((-diff)*100, start_activity)
        return
    end

    -- todo 省略活动内容
end

以上代码,通过 skynet.timeout 设置定时器,定时触发 start_activity , 看似没有问题,但是,当 skynet.timeout 第一个参数过大时,会有意想不到的收获。
继续阅读lua定位CPU100%问题

lua5.4 分代垃圾回收

前注

最近我看了 lua 5.4 分代垃圾回收的代码,网上很少讲到这块内容,于是,我写这篇文章分享一下,也当做总结。文章就以我看的 lua 5.4.4 做分析。

在进入今天的主题前,先回顾我之前写的 lua5.3 垃圾回收分析,重复的内容不再赘述。

lua 5.4 支持两种 gc 方式, 增量式 gc 和分代 gc ,默认是增量式,可以通过以下 api 切换。

切换成分代 gc:
collectgarbage "generational"
切换成增量式 gc:
collectgarbage "incremental"

提出问题

1. 增量式 gc 跟分代 gc 的对比?
2. 分代 gc 过程是怎样的?
3. 分代 gc 有哪些状态?
4. 分代 gc 怎么调优?

继续阅读lua5.4 分代垃圾回收

找到 lua 死循环代码

最近遇到 lua 死循环的情况,就研究下如何找到死循环所在的代码,方便定位和解决问题。

首先,要找到出问题的协程(lua_State)。lua vm 实现时,每个协程都有自己独立的堆栈(stack),以及函数栈(CallInfo)。

所以,要先找到死循环的协程,再从这个协程函数栈找到代码。

怎么找到这个协程?

有三种办法:
方法1、 在死循环会执行到的 opcode ,加代码取得当前协程;
方法2、 遍历所有 gc 数据对象,找到当前正在执行的协程;
方法3、 重写 coroutine.resume 及 wrap 函数,取得当前协程。
继续阅读找到 lua 死循环代码