lua的GC

关于lua的gc云风大佬在 Lua GC 的源码剖析 系列文章中讲得很清楚,这里做一下简单的记录。

分步gc

lua使用的是一种三色标记清除算法(tri-color incremental mark & sweep),大体步骤如下:
初始阶段,所有对象标为白色;
标记阶段的开始,将所有从root可达的对象标记为灰色;
标记阶段,逐个取出灰色对象,将其本身标记为黑色,将其所有引用的白色对象标记为灰色;
当不存在灰色对象时,进入清除阶段,清除所有白色对象,将所有黑色对象标记回白色。

具体而言,lua的gc分为markroot、mark、atomic、sweepstring、sweep、finalize几个阶段(见singlestep())。
markroot函数将若干lua对象链表的根节点置为灰色,放入gray链表/队列;mark阶段类似树的广度优先遍历,从gray队列中取出灰色对象,标为黑色,再将其所引用的对象置为灰色,加入gray队列;mark是分步执行的,中间对象关系可能又发生变化,在开始清理工作前,还需要做最后一次扫描,这个过程不可以再被打断,就是atomic函数;sweepstring阶段分步清理字符串;sweep阶段分步清理其他对象;userdata对象可能会有自定义gc方法,finalize阶段就用来调用这些gc方法,userdata对象本身则放在下轮gc清理。

标记阶段和清除阶段都是可以分步的,所以称为分步gc。但中间可能出现这样的情况:A引用B,B引用C;A已经标记为黑色,B标记为灰色,C为白色;在gc间歇期间,程序修改了对象间的引用关系, B不再引用C,而A开始引用C。C本来是活跃对象,但却会被清除,这会造成严重错误。算法引入写屏障技术,来解决这种问题:将此 白色 对象标记成 灰色,称为barrier forward,因为正常是白色->灰色->黑色的转换方向;将此 黑色 对象标记回 灰色,称为barrier back。

如何确定gc的步伐大小是一系列的微操,见singlestep()、luaC_setp()。

数据类型

在Lua中共有9种数据类型,分别为nil、boolean、lightuserdata、number、string、table、function、userdata和thread。其中只有string table function thread四种在vm中以引用方式共享,是需要被GC管理回收的对象。其它类型都以值形式存在。
另外还有两种类型的对象需要被GC管理,分别是proto和upvalue。

string创建后挂载于g->strt->hash
upvalue创建后被链在g->uvhead
table、thread、function、proto则都是挂在g->rootgc上,在mark阶段简单加到gray队列即可

markroot

有3个链表用于标记和清理过程,gray是灰色对象的链表,grayagain是在atomic阶段需要再次标记的对象的链表,weak是弱表的链表。
markroot()先将这三个链表清空,再将mainthread、mainthread的全局表、注册表、各类型元表标记,放到gray队列中,开始mark阶段。

string

mark的时候只是置灰,不挂载到gray上,所以它没有黑色。
在sweepstring阶段集中处理g->strt->hash。

userdata

rootgc初始化为mainthread,创建其他对象时使用的是头部插入,所以mainthread是链表的最后一个元素。
但是luaS_newudata()中将userdata挂到mainthread后,所以rootgc链表被mainthread分为两部分,后边是userdata,前边是其他对象。
userdata没有灰色,mark时直接标为黑色。
atomic中遍历mainthread之后的userdata链表(luaC_separateudata()),空闲且有gc方法的从rootgc移到g->tmudata中。
finalize阶段专门用来处理tmudata链表,对每个元素标记为白色,返还给rootgc,然后调用它的gc方法。
没有gc方法的空闲数据,就在sweep阶段被清理掉;有gc方法的,第一轮gc时先调用gc方法,第二轮gc时在sweep阶段清理掉userdata本身,通过finalized标志来识别userdata的状态。

string和userdata不引用其他对象,都是叶子节点。

upval

mark的时候:
如果是opend的,其指向栈上变量,将其指向的变量变灰;
如果是closed,其已是叶子节点,直接变黑。

以下引用自 Lua GC 的源码剖析 (4):

为何 open 状态的 TUPVAL 需要留为灰色待处理呢?这是因为 open TUPVAL 是易变的。GC 分步执行,我们无法预料在 mark 流程走完前,堆栈上被引用的数据会不会发生变化。事实上,在 mark 的最后一个步骤,我们会看到所有的 open TUPVAL 被再次 mark 一次,做这件事情的函数是 remarkupvals。

thread

mark的时候,从gray移到grayagain,且不变黑。
堆栈是随着运行过程不断变化的,为了效率其上数据的修改是不经过barrier的,所以把它推迟到atomic阶段重扫描。

table

traversetable()的时候,弱表不会从灰变黑,而是转移到weak链表上。
若弱表引用的元素被移除,也需要将元素从弱表中移除,atomic()中会调用cleartable()来做这件事。
移除table中hash部分value为nil的entry是通过removeentry():

static void removeentry (Node *n) {lua_assert(ttisnil(gval(n)));if (iscollectable(gkey(n)))ttype(gkey(n)) = LUA_TDEADKEY; /* dead key; remove it */
}

可以看到只是将key设为LUA_TDEADKEY类型,并没有从表中真删掉,那何时真正删除呢?是rehash的时候。
所以 高性能 Lua 技巧(译) 中这样说“你不该期望通过从一个大表里删除一些数据来回收内存,更好的做法是删除这个表本身。”。

参考

讲解 Lua 内部实现的 gc 机制
Lua GC 的工作原理
Lua GC 的源码剖析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/30174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【FreeRTOS】估算栈的大小

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》 目录 估算栈的大小回顾简介计算说明估计函数用到的栈有多大合计 估算栈的大小 回顾 上一篇文章链接:http://t.csdnimg.cn/Cc8b4 传送门: 上一篇文章 上一篇文章创建的三个任务 /* 创建任务:声 *…

22.HandlerPipeline

ChannelHandler用来处理Channel上的各种事件,分为入站和出站两种。 所有的ChannelHandler被连成一串,就是Pipeline。 入站处理器通常是ChannelInboundHandlerAdapter的子类,主要用来读取客户端数据,写回结果。 出站处理器通常是ChannelOutboundHandlerAdapter的子类,主…

Uni-app x

uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。 uni-app x 是一个庞大的工程,它包括uts语言、uvue渲染引擎、uni的组件和API、以及扩展机制。 uts是一门类ts的、跨平台的、新语言。uts在iOS端编译为swift、在Android端编译为kotli…

LabVIEW开发为何仿制(致敬)经典成熟软件

​仿制(致敬)成熟软件进行LabVIEW开发更方便,因其提供了稳定的架构和结构、优化的用户体验和界面设计、技术规范和标准、稳定性和可靠性。结合用户手册和现有操作进行仿制,就像小米致敬保时捷一样,可以提高开发效率、降…

vivado SITE

描述 SITE是一个设备对象,表示许多不同类型的逻辑资源之一 可在目标Xilinx FPGA上获得。 SITE包括SLICE/CLB,它们是基本逻辑元件(BEL)的集合,如 查找表(LUT)、触发器、多路复用器,携…

QT/基于TCP的服务端实现

代码 widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget),p(new QTcpServer(this))//给服务器指针申请空间 {ui->setupUi(this); }Widget::~Widget() {delete ui; }void W…

实测:三款垃圾CPU推理Llama3 8B

经常有一些朋友问我本地运行大模型的电脑需要什么样的配置。其实一些常用大模型的运行需要的硬件并不像我们想象的那样高不可攀。不要被那些复杂的技术术语所吓倒,关键在于亲自动手尝试。 “不试,怎么知道呢?” 这句话道出了真理。今天&#…

【Linux】基础IO_1

文章目录 六、基础IO1. C语言的文件接口2. 系统文件I/O 未完待续 六、基础IO 1. C语言的文件接口 我们知道 文件 文件内容 文件属性 。即使是一个空文件,仍然会在磁盘中占据空间。那打开文件是什么意思呢?其实文件打开的意思就是:将文件从…

package cmp is not in GOROOT (/usr/local/go/src/cmp)

具体错误 env CGO_ENABLED0 go build -trimpath -ldflags "-s -w" -tags frpc -o bin/frpc ./cmd/frpcclient/admin_api.go:18:2: package cmp is not in GOROOT (/usr/local/go/src/cmp)pkg/util/util/util.go:23:2: package math/rand/v2 is not in GOROOT (/usr/…

力扣每日一题 6/19 排序+动态规划

博客主页:誓则盟约系列专栏:IT竞赛 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 2713.矩阵中严格递增的单元格数【困难】 题目: 给你一个下标从…

【学习DayNa】信息系统开发整理

✍🏻记录学习过程中的输出,坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞👍🏻收藏⭐评论✍🏻指点🙏 结构化方法 结构是指系统内各个组成要素之间的相互联系、相互作用的框架。结构化开发方法就是…

Java应用打包成Docker镜像

# 使用官方的OpenJDK17镜像作为基础镜像 FROM openjdk:17 # 设置工作目录 WORKDIR /app # 复制本地的Java应用程序文件到镜像中的指定目录 COPY target/bear-module-system-0.0.1-SNAPSHOT.jar /app/bear-module-system-0.0.1-SNAPSHOT.jar # 暴露API端口 EXPOSE 8888 …

shell中的流程控制

条件判断在流程控制中的重要性 有了条件判断才能进行if判断即分支流程,才能进行case的多分支流程,才能进行for循环和while循环。 单分支流程判断 如上图所示,在shell编程中常使用英文状态下的分号来在Linux控制台一次性执行多条命令&#x…

小米SU7遇冷,下一代全新车型被官方意外曝光

不知道大伙儿有没有发现,最近小米 SU7 热度好像突然之间就淡了不少? 作为小米首款车型,SU7 自上市以来一直承载着新能源轿车领域流量标杆这样一个存在。 发售 24 小时订单量破 8 万,2 个月后累计交付破 2 万台。 看得出来限制它…

运算放大器(运放)缓冲器(跟随器)电路

运算放大器(Operational Amplifier) 运算放大器(Operational Amplifier)是一种差分放大器,具有高输入电阻、低输出电阻、高开放增益(开环增益),并具有可放大输入引脚与-输入引脚间的电压差的功能。 设计目标 输入输入输出输出频…

“打卡展示”后遗症如何解决

最近看了一本书,里边提到的学习打卡,健身打卡,早期打卡,不管是在家里,在学校,上班族,都在抓紧一切时间提升自己,为了实现目标出现了形形色色的打卡,只要你能想得到的领域…

HTML李峋同款跳动的爱心代码(双爱心版)

目录 写在前面 跳动的爱心 完整代码 代码分析 系列推荐 最后想说 写在前面 在浩瀚的网络世界中,总有一些小惊喜能触动我们的心弦。今天,就让我们用HTML语言,探索既神秘又浪漫的李峋同款跳动的爱心代码吧。 首先,让我们一起…

基于Pytorch框架的深度学习Swin-Transformer神经网络食物分类系统源码

第一步:准备数据 5种鸟类数据:self.class_indict ["苹果派", "猪小排", "果仁蜜饼", "生牛肉薄片", "鞑靼牛肉"] ,总共有5000张图片,每个文件夹单独放一种数据 第二步&…

分布式锁(4):jedis基于Redis setnx、get、getset的分布式锁

1 实现原理 setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向步骤(2)get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,…

《多线程》

每一个任务就是一个进程,每个进程内部至少有一个线程在运行中。线程是程序执行的一个路径,每一个线程都有自己的局部变量表,程序技术器,以及各自的生命周期。 1.创建一个线程,并且重写它的run方法,将行为方…