Lua GC 对数据类型的特殊处理

本文详细探讨了Lua的垃圾回收机制,重点分析了luaC_barrierback和luaC_barrierf的区别及应用场景,包括对table、string、thread、upvalue、userdata等不同类型数据的处理方式,以及在GC各阶段的具体运作。

luaC_barrierback和luaC_barrierf 区别
在GC过程中对新建的对象引用关系进行处理。
例如:o 引用 v
luaC_barrierf : 扫描阶段将v重新标记(V加入到当前GC),否则将o标记为white
luaC_barrierback:将o加入到grayagain中。grayagain在原子操作中进行处理 

luaC_barrierback用于监控table的key和value 变化。因为table是易变的,如果使用luaC_barrierf ,那么每次改变key或value都重新扫描新对象,导致标记时间的增长。
所以使用luaC_barrierback当发生改变时,见其加入grayagain中,这就将table分为了两种,一种是未改变,一种是改变的,再次发生改变时也不会重复加入到grayagain中。
等待amotic阶段重新扫描真个table表

String:
字符串存储在开散列的hash表中,GCSsweepstring 每次清理散列表中的一列。由于清理也是分步进行的,当在字符串清理阶段发生resize散列表,有可能未清理部分的字符串会放置到以清理部分,
导致字符串没办法在当前GC重置为white。

table:
weak table 只有在所有对象mart完才能确定哪些节点需要释放,所以weak table 一直为gray(引用对象未遍历),所以对weak table的修改不会触发 barrier(只有black的节点才会触发barrier)。
所以在mart 阶段结束后,table中可能还会存在一些强引用。所以在atomic 阶段对weak table 进行remart,所有的weak table在 mart阶段都被放入到g->weak链表中。


线程:

线程引用的是线程堆栈,在运行时整个堆栈都是一直变化的,如果加入luaC_barrier监控变化,会导致运行效率降低,所以Lua默认线程就是一直在变的,在mart是不会标记为black,而是像处理table一样在amotic阶段重新扫描。


upvalue:
新建的upvalue(打开状态)存储在g->uvhead和L->openupval中,不连接在alloc链表上。当close upvalue时将upvalue放回到alloc中并从openupvalue和uvhead中移除。所以处理GC处理upvalue时,如果是close upvalue 则像普通对象一样处理。open upvalue则需要特殊处理。openupvalue 只有可能被线程和lua  closure引用,而只被线程引用而没被lua  closure引用的upvalue 是无意义的,应该被清除掉。所以线程对upvalue是弱引用。当mart阶段只会将open upvalue 从white 变为 gray,用于标记是否被lua  closure引用。在清理阶段,线程对象遍历自己的upvalue ,清理掉没有被引用到的upvalue。当线程退出时也会清理open  upvalue。open upvalue 由于引用栈上的值,变化频繁,所以也需要在atomic阶段重新remart。


userdata处理:
由于userdata由_gc元方法存在,清理之前应该调用_gc元方法。所以需要遍历所有的userdata,Lua将userdata存储在alloc链表中,但是区别于其他对象头插入到alloc中,userdata插入到线程对象之后,其他对象-->线程-->userdata。在atomic阶段 通过luaC_separateudata 查找所有已经"dead",并将带_gc 方法的userdata,将其从alloc链表中移除,加入到tmudata双向链表中,并标记FINALIZEDBIT,防止_gc元方法重复调用。在GCSfinalize阶段遍历tmudata,调用_gc元方法并将其从tmudata中移除,重新加入到alloc链表中。dead的userdata并不在本次gc中回收,而是先做标记然后调用_gc元方法,然后在下一次的GC中回收。


base type metatable:
基本类型的元表是共享的,而不是像table 等 是每个对象独享的。所以 base type metatable 存储在 g->mt中(也在alloc中)。当设置元表时,table 等会触发barrier,但是base type metatable 由于存储结构关系,barrier无法处理,所以并不会触发barrier。所以在atomic阶段会重新remart   base type metatable(mart 过程中引用关系的改变)。

Lua 的 `table` 是 Lua 中最核心、最灵活的数据结构之一。它不仅是 Lua 中唯一内置的复合数据类型,还承载了数组、字典、对象、模块等多种语义。理解 `table` 的内部实现原理对于深入掌握 Lua 性能优化和设计模式具有重要意义。 ### 表的底层结构 Lua 的 `table` 在底层由两个主要部分组成:**数组部分**和**哈希部分**。这种设计使得 `table` 能够高效地支持整数索引和非整数索引两种访问方式。 - 数组部分用于存储整数键(从 1 开始的连续整数),以紧凑的方式存储在数组中,访问速度非常快。 - 哈希部分则用于存储任意类型的键(包括字符串、表、函数等),采用开放寻址法实现的哈希表来管理这些键值对。 这种双数组结构的设计使得 Lua 表在处理不同类型的键时都能保持良好的性能[^2]。 ### 表的元表与元方法 Lua 中的 `table` 可以通过设置**元表(metatable)**来改变其行为。元表本质上也是一个 `table`,其中可以定义特殊的**元方法(metamethods)**,如 `__index`、`__newindex`、`__add` 等。这些元方法允许开发者定义表在特定操作下的行为,例如访问不存在的字段、进行加法运算等。 - `__index` 元方法用于定义当访问一个表中不存在的键时的行为。它可以是一个表,也可以是一个函数。 - `__newindex` 用于定义对表中不存在的键进行赋值时的行为。 - `__call` 使得表可以像函数一样被调用。 通过元表机制,Lua 实现了面向对象编程中的继承、方法调用链、运算符重载等功能[^3]。 ### 表的内存管理与性能优化 Lua 使用自动垃圾回收机制(GC)来管理 `table` 的内存。每个 `table` 在创建后会由 Lua 的垃圾回收器自动追踪其生命周期,并在不再被引用时回收其占用的内存资源。 Lua 的 `table` 实现中还包含了一些优化策略,例如: - **预分配策略**:当一个表被创建时,Lua 会根据其初始大小预分配一定的数组和哈希空间,以减少后续插入操作时的内存分配次数。 - **重哈希(Rehash)**:当哈希部分的键值对数量超过一定阈值时,Lua 会重新分配更大的哈希表并重新计算哈希值,以保持查找效率。 此外,Lua 提供了 `table.create` 和 `table.clear` 等函数用于更精细地控制表的内存使用,尤其在性能敏感的场景中(如游戏开发)非常有用[^1]。 ### 示例:使用元表实现继承 ```lua -- 基类 Base = {} function Base:new() local obj = {} setmetatable(obj, self) self.__index = self return obj end -- 派生类 Derived = Base:new() function Derived:hello() print("Hello from Derived") end -- 创建实例 local d = Derived:new() d:hello() -- 输出 "Hello from Derived" ``` 在这个例子中,`Base:new` 方法通过设置元表和 `__index` 实现了类的继承机制。这种模式广泛用于 Lua 的面向对象编程中[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值