LuaDepth
协程
线程:抢占式多任务机制,是一个相对独立的、可调度的执行单元,是系统独立调度和分配CPU的基本单位。它由操作系统来决定执行哪个任务,在运行过程中需要调度,休眠挂起,上下文切换等系统开销,而且最关键还要使用同步机制保证多线程的运行结果正确
协程:协作式多任务机制,协程之间通过函数调用来完成一个既定的任务。它由程序自己决定执行哪个任务,只涉及到控制权的交换(通过resume-yield),同一时刻只有一个协程在运行,而且无法外部停止。通俗来说,协程就是可以用同步的方式,写出异步的代码
协程(Coroutine)拥有4种状态:
- 运行(running)如果在协程的函数中调用status,传入协程自身的句柄,那么执行到这里的时候才会返回运行状态
- 挂起(suspended)调用了yeild或还没开始运行,那么就是挂起状态
- 正常(normal)如果协程A重启协程B时,协程A处于的状态为正常状态
- 停止(dead)如果一个协程发生错误结束,或正常终止。那么就处于死亡状态(不可以再重启)
Lua的协程是一种非对称式协程,又或叫半协程,因为它提供了两种传递程序控制权的操作:1. 重启调用协程,通过coroutine.resume实现;2. 挂起协程并将程序控制权返回给协程的调用者,即通过coroutine.yield实现。对称式协程,只有一种传递程序控制权的操作,即将控制权直接传递给指定的协程
协程(Coroutine)具有两个非常重要的特性:1. 私有数据在协程间断式运行期间一直有效;2. 协程每次yield后让出控制权,下次被resume后从停止点开始继续执行
闭包
把函数原型换成类,把闭包换成对象。一切就都可以解释了,无论什么时候,把一个函数原型赋值给一个变量时,Lua都会将这个函数原型实例化为一个闭包对象,无论这个闭包对象是否真的用到了所谓的上值表。当在Lua中调用函数或者传递函数对象时,都是在传递闭包对象,而非传递函数原型。
所以说白了,闭包就是函数原型的一个实例
所以它为什么叫闭包呢?就是因为当实例化一个函数原型为一个闭包时,这个函数原型已经捕获到了自己需要所有的上值对象,此后不会再进行任何其他捕获行为。它形成了一个自洽的体系,有自己内部的常量表,上值表,变量表。
这就是Lua的闭包机制,一种用到了OOP思想的机制。
- Upvalues是在函数闭包生成的时候(运行到function时)绑定的
- Upvalues在闭包还没关闭前(即函数返回前),是对栈的引用,这样做的目的是可以在函数里修改对应的值从而修改Upvalues的值
- 闭包关闭后(即函数退出后),Upvalues不再是指针,而是值
userdata
userdata本身只是一个指针(light userdata),或一块受Lua管理的内存块(full userdata),它没有任何预定义行为,在Lua看来它就是一个值,你需要提供配套的C函数去操作它。一般可以用一个对应的元表来判断userdata的类型,以及用一些方法操作userdata的属性
Json
1. 高效的内存管理
- 预分配内存策略: 使用
membuffer
结构管理缓冲区,提前分配足够空间 (membuffer_ensure_space
),避免频繁重分配 - 内存复用: 尽可能复用内存缓冲区,减少内存分配次数
- 零拷贝优化: 直接在缓冲区构建结果,避免中间字符串拷贝
2. 优化的解析技术
- 高效分派: 使用 switch-case 和查表法代替条件分支
- 内联优化: 关键函数使用
inline
,减少函数调用开销 - 分支预测: 使用
likely
和unlikely
宏优化 CPU 分支预测 - 快速路径: 常见情况直接处理,避免通用路径的开销
3. 数值处理专项优化
- 整数/浮点分离处理: 对整数和浮点数采用不同的处理路径
- 延迟转换: 尽量保持整数形式,只在必要时转为浮点数
- 定界检测: 通过
MAXBY10
和MAXLASTD
常量快速判断整数边界 - 幂运算优化: 使用预计算的
powersOf10
表加速指数部分计算
4. 字符串处理优化
- 转义表: 使用预构建的查表法处理字符转义 (
char2escape
和escape2char
) - 直接缓冲区操作: 使用
_unsafe
系列函数在保证安全的情况下跳过边界检查 - UTF8/16 高效编码: 针对 Unicode 编码进行了特殊优化
5. 智能表处理
- 数组/对象智能判断: 根据数据特征自动识别是数组还是对象
- 空表优化: 对空表进行特殊处理,可配置为数组或对象
- 表遍历优化: 减少表遍历中的 Lua API 调用次数
6. 底层 Lua API 优化
- 栈操作优化: 最小化 Lua 栈操作,减少入栈/出栈开销
- 直接数据访问: 直接与 Lua 数据类型交互,避免不必要的类型转换
- 预分配栈空间: 使用
luaL_checkstack
提前确保足够的栈空间
7. 其他技术优化
- 基于事件回调: 使用事件回调机制分离解析和构建逻辑
- 深度控制: 防止过深嵌套导致的栈溢出
- 条件编译: 根据需要开启格式化或注释支持
这些优化使得 LuaExtend JSON 实现在各种场景下都能保持高性能,尤其在处理大型 JSON 数据或高频解析/序列化场景中优势更加明显。
List
如下面这样的代码:
local t = {}
local tinsert = table.insert
for i = 1, 1000 do
tinsert(t, i)
end
上面的代码还可以再优化成这样:
local t = {}
for i = 1, 1000 do
t[#t+1] = i
end
这减少了一次C API
的调用,性能会快一点点。
看起来#t
并不是立即返回数组长度,而是做了更耗时的操作,翻开Lua
的源码,相关的函数是:
lua_Unsigned luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (isdummy(t)) { /* hash part is empty? */
return j; /* that is easy... */
}
else {
return unbound_search(t, j);
}
}
问题就在if (j > 0 && ttisnil(&t->array[j - 1]))
这一段,如果数组部分最后一个元素是nil
,那么就用二分查找找到最后不是nil
的元素,返回这个索引。
我们不断地往Table增加数据时,数组部分会动态地按2的幂扩充,比如现在数组长度是2,再加一个值,数组就动态扩充成4,后面再扩充成8,16…,多出来的槽位填充为nil。所以除非数组刚好满了,否则#t
都会用二分查找来确定长度。时间复杂度一样子从O(1)
变成了O(logN)
。这样分析下来,甚至比Python慢也是可以理解了。
这个问题的根源,我认为是Lua
把哈希表和数组融合在一起,很多对数组的操作,也不得不考虑哈希部分;而且在rehash的时候,数据经常在数组和哈希表转移。这种融合在我看来一点好处都没有:增加了Table代码的复杂度,且很难针对数组的操作作出更优化的写法。比如没有办法提供数组的精确长度,只能通过二分查找找到最右边的非空元素。
在Lua
中,把Table当成数组操作,是会比其他语言承担更多的性能开销的。
-
用户数据设计:使用
lua_newuserdata
创建 C 结构体,配合元表实现对象化 -
引用系统:通过自定义引用表管理 Lua 值的存储,避免 GC 问题
-
元表机制:每个模块定义专有元表和元方法,支持面向对象语法
-
类型检查与转换:使用
luaL_check*
系列函数确保类型安全 -
动态扩容:如列表/队列等动态数据结构实现自动扩容算法
-
内存复用:尽可能复用内存,减少分配/释放次数
-
安全释放:使用
__gc
元方法确保资源自动释放 -
定制内存分配器:通过
_malloc
/_realloc
/_free
封装内存操作
序列化
1. 高效的序列化方案
- 优化的二进制格式设计:使用 type+cookie 8位字节高效编码类型信息
- 自适应数值编码:根据数值范围自动选择 int8/16/32/64,节省空间
- 表结构优化:分离数组部分和哈希部分,减少序列化体积
- 递归深度控制:通过 MAX_DEPTH 参数防止栈溢出
- 可讨论点:与 JSON/MessagePack 相比的性能优势
2. 内存管理策略
- 内存分配最小化:使用固定初始缓冲区(INIT_BUFF_SIZE)减少小对象分配
- 智能扩容算法:缓冲区动态扩容时采用倍增策略,平衡性能与内存使用
- 内存复用机制:尽可能重用已分配内存,避免频繁分配/释放
- 可讨论点:在内存受限环境中的优化思路
3. 跨平台兼容性
- 字节序处理:通过 nativeendian 智能检测并处理大小端差异
- 整数跨平台表示:处理不同平台整数位宽差异
- 可讨论点:如何确保数据在异构系统间安全传输
4. C/Lua 接口设计
- 面向对象包装:使用元表机制提供自然的 OOP 接口
- 引用系统:通过引用表机制安全管理 Lua 对象生命周期
- 统一命名空间:所有模块使用 LuaExtendLib 前缀,避免命名冲突
- 可讨论点:如何平衡接口易用性与底层性能
5. 性能优化技术
- 内联关键函数:使用 inline 避免高频调用开销
- 自定义内存分配:通过 _malloc/_realloc/_free 封装,便于优化和跟踪
- 底层优化:如 JSON 解析器中的字符级优化和查表法处理
- 算法优化:如快速排序、二分查找等高效算法实现
- 可讨论点:性能瓶颈分析方法和优化策略