Lua gc 机制版本迭代过程简述

这篇具有很好参考价值的文章主要介绍了Lua gc 机制版本迭代过程简述。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

内存自动化管理的概念

内存自动化管理是指在指定内存不再被需要时可以自动被释放。通常有两种方案来实现内存的自动化管理:

  • 引用计数 (reference count)

  • 垃圾回收 (garbage collection)

Lua 选择的是垃圾回收的方案。不选择引用计数的方式的主要原因是,在动态类型语言中,使用引用计数来管理内存,即使是在没有申请任何内存的情况下也会有额外的开销。这里的额外开销主要是 cpu 开销,例如你只是操作了已分配内存的数据,没有分配新的内存,你也需要更新引用计数。

Lua 中的 gc 对象

Lua 中的所有对象都接受自动管理:tables, userdata, functions, threads, and strings. 在 Lua 运行期间,所有的 Lua 对象都链接到一个内置链表中,以供垃圾回收器可以找到他们。

何时对象需要被回收:当对象不再能从根集(root set)中访问到。

根集包括 Lua 注册表(registry)和共享元表(share metatable):

  • Lua 注册表:

    • 全局表 _G

    • 主协程(lua 栈)

    • package.loaded (加载的 modules)

  • 共享元表:

    • 被其他对象设置为元表的 table

Lua 中内存分配和释放的底层接口

Lua 不直接操作内存,例如向系统申请一大块内存来进行更细致的管理,而是直接使用标准 c 库的内存接口,例如 malloc、free 和 realloc。这样在一方面的确是有不足,使得 Lua 的内存管理依赖具体操作系统环境的内存管理机制的性能好坏,但是另一方面,Lua 通常是和 c 或其他语言搭配使用,这样开发人员可以为应用使用一套分配机制,而不会导致在应用中存在两种分配机制,一种服务于 Lua,一种服务于另外的语言。

Lua 5.0 gc 算法

基础的标记 + 清扫:

  • 标记:从根集开始,遍历对象关联图,标记存活的对象。

  • 清扫:遍历链接所有 Lua 对象的内置链表,删除那些没有被标记的对象。

void luaC_collectgarbage (lua_State *L) {
    mark(L);
    luaC_sweep(L, 0);
    checkSizes(L);
    luaC_callGCTM(L);
}

收集器步子大小的影响:

  • 极端情况一:从不启动收集,在收集器上没有任何 cpu 开销,但是内存压力很大,只会上涨不会下降。

  • 极端情况二:始终在运行收集器,cpu 开销巨大,但是内存情况维持得很好。

  • 比较合适的设置:当内存增长到上一次垃圾回收后的内存的两倍大小时开始新的一轮回收。

主要的缺陷:

由于 gc 过程是一个连续的不可中断的过程,一旦开始 gc,应用程序必须等到一次完整 gc 执行完毕才可继续运行,这个等待时间对大多数 cpu 密集型应用是不可接受的,这也是 Lua 5.1 新的 gc 机制出现前 Lua 的应用场景并没有之后那么广泛的重要原因。

Lua 5.1 gc 算法

主要的改进:收集器可以增量式地跟应用程序交替运行。

三色标记回收算法 (Tri-color Collector):

  • 状态说明:

    • 每一个 Lua 对象都处于白色、灰色和黑色三种状态之一。

    • 不能访问到的对象被标记成白色。

    • 可以访问到但是没有遍历到的对象被标记成灰色。

    • 被遍历到的对象被标记成黑色。

  • 约束条件:

    • 在根集中的对象是黑色或者灰色。

    • 一个黑色对象不能指向一个白色对象。

    • 灰色对象的存在形成了白色对象和黑色对象之间的隔离线,如下图红线所示。

    • 收集器通过遍历灰色对象来推进工作,将他们转换成黑色,在这个过程中可能会创建新的灰色对象。

    • 当不再存在灰色对象时,收集器结束工作。

Lua gc 机制版本迭代过程简述,lua,算法学习,lua

修改器(Mutator):从垃圾收集器的角度来看,应用程序就像一个修改器一样在不断地改变那些被收集器试图收集的数据,对收集器的工作造成干扰。

屏障(Barriers):向前它可以将白色对象转化成灰色对象,向后它可以将黑色对象转化成灰色对象。

启发式设计(heuristics):

  • 从黑色回退到灰色的对象暂存到一个分离出去的链表中,仅供在 atomic 阶段拿来遍历,这样可以避免出现 ping-pong 现象(反复横跳)。

  • 栈中对象保持为灰色,避免当往栈中写入的时候触发写屏障。

    • 栈中对象通常来说是比较少的,但是赋值操作又比较频繁,所以这样做来避免写操作触发屏障检查是有收益的,代价是在 atomic 阶段栈需要遍历栈中对象。
  • 对 table 进行赋值操作,会将黑色的 table 回退到灰色。

    • 试想如果不这么处理,在对黑色 table 进行多次赋值时(a[x] = y),赋值的对象 x, y 会被标记成黑色,即使可能在后续某次赋值时 y 被替换掉(a[x] = z),这样就产生了额外开销的标记工作,如果将黑色 table 回退成灰色就可以避免掉这类额外开销。
  • 设置元表的操作,Lua 5.4 中已经修改为当被设置元表的对象是黑色并且元表是白色的情况下,将元表颜色向前转换为灰色,这是因为元表通常会被分配给许多不同的对象,并且元表比其他对象更加稳定。

    • Lua 5.1 和 Lua 5.2 中是将黑色的 table 回退成灰色。

原子操作阶段(atomic):

  • 这是标记流程的最后一步。

  • 在这个阶段会遍历 “gray again” 链表和栈对象,这块对象的量级可以认为很小,所以该阶段在整个回收流程的占比很小,可以认为该阶段相比于整个回收流程来说进行地非常迅速。

  • 检查并清理弱表。

  • 分离出确定复活的对象,复活他们以及关联对象。

  • 再次检查并清理弱表。(有一些弱表复活了)

收集器步子大小控制参数:

  • 暂停时间:通过检测距离上次回收结束增长了多少内存来触发下一次回收启动。一般设置为固定值 “2”, 当内存增长到上次的两倍时开始新的一轮回收。调用 collectgarbage ([opt [, arg]]) 接口设置,opt = “setpause”,arg = 200 表示两倍内存阈值。

  • 回收倍率:控制以分配速度为基准的回收的相对速度,通过 collectgarbage ([opt [, arg]]) 接口设置,opt = “setstepmul”,arg = 200,最好不要设置为小于 100 的值,它会导致垃圾回收器推进的十分缓慢,甚至都无法完成一次完整的 gc。详情参考 Lua 5.2 manual 2.5 节

注意:

  • 增量式回收算法虽然缩短了每次暂停主程序运行的时间,但是它并没有减少收集器进行一次完整 gc 带来的开销,恰恰相反它的总开销是更高的。

Lua 5.2 - Lua 5.4 gc 算法

Lua 5.2 在 Lua 5.1 增量回收的基础上提出了一个实验性的 gc 模式,分代模式:

As an experimental feature in Lua 5.2, you can change the collector’s operation mode from incremental to generational. A generational collector assumes that most objects die young, and therefore it traverses only young (recently created) objects. This behavior can reduce the time used by the collector, but also increases memory usage (as old dead objects may accumulate). To mitigate this second problem, from time to time the generational collector performs a full collection. Remember that this is an experimental feature; you are welcome to try it, but check your gains.

Lua 5.4 对 Lua 5.2 中的分代模式进行了一些优化,并且完善了世代模式成为一种正式的 gc 机制:

The garbage collector (GC) in Lua can work in two modes: incremental and generational.

The default GC mode with the default parameters are adequate for most uses. However, programs that waste a large proportion of their time allocating and freeing memory can benefit from other settings. Keep in mind that the GC behavior is non-portable both across platforms and across different Lua releases; therefore, optimal settings are also non-portable.

You can change the GC mode and parameters by calling lua_gc in C or collectgarbage in Lua. You can also use these functions to control the collector directly (e.g., to stop and restart it).

分代模式的 gc 机制在不少脚本语言的 gc 机制中存在,例如 c#,它基于以下假设:

大多数对象在早期就死亡了,所以,收集器可以专注于回收比较年轻的对象。

当这个假设成立的时候,分代模式的 gc 总开销是小于增量式的。 假设不成立的典型情形是批处理程序。

简单介绍:

  • 所有对象被划分为年轻的和年老的。刚创建的对象都是年轻的。在 Lua 5.4 中,需要存活超过两个回收周期,对象才会变成年老的,而在 Lua 5.2 中,只需要存活超过一个回收周期。

  • 收集器进行频繁的次级回收(minor collection),只遍历和清理年轻的对象。

  • 这里简单对比下存活不同周期后变成年老对象的区别:

    • Lua 5.2 年轻对象存活一个周期后变成年老对象

      • 简单许多的代码实现。

      • 在一次回收周期结束后,所有存活的对象都变成年老的。

      • 接触列表(touched list)中的对象可以移除出来。

      • 对象在变老之前可能只存活了无穷小的时间间隔,如图:

      • Lua gc 机制版本迭代过程简述,lua,算法学习,lua

    • Lua 5.4 年轻对象存活两个周期后变成年老对象

      • 在一次回收周期结束后,一些对象变成老的,一些没有。

      • 接触列表(touched list)中的对象需要被正确调整以能正常进行下次回收。

      • 对象在变老之前至少需要存活一个完整的 gc 周期,如图:

      • Lua gc 机制版本迭代过程简述,lua,算法学习,lua

  • 约束条件:

    • 一个年老对象不能指向一个年轻对象。
  • 接触列表(touched list):

    • 如果检测到一个老对象正指向一个年轻的对象,这个老对象会被标记为接触者(touched)并且被放到一个特殊的列表中(touched list)。

    • 在两次回收周期之后,一个接触者对象会恢复到正常的年老状态,除非它又再次被接触(指向一个年轻对象)。

    • Lua gc 机制版本迭代过程简述,lua,算法学习,lua

选择哪个模式:

正如官方手册所讲,带有默认参数的默认GC模式适用于大多数用途。然而,浪费大量时间分配和释放内存的程序可以从其他设置中受益。请记住,GC行为是不可移植的,无论是跨平台还是跨不同的Lua版本;因此,最佳设置也是不可移植的。需要开发人员根据自己的应用特性和机器特性进行不断的调整和测试来寻求合适的解决方案。文章来源地址https://www.toymoban.com/news/detail-602073.html

参考内容

  • Lua 各版本手册
  • Roberto Ierusalimschy 演讲 ppt
  • Roberto Ierusalimschy 演讲 video

到了这里,关于Lua gc 机制版本迭代过程简述的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM学习 GC垃圾回收机制 (堆内存结构、GC分类、四大垃圾回收算法)

    🤖 作者简介: 努力的clz ,一个努力编程的菜鸟 🐣🐤🐥   👀 文章专栏: 《JVM 学习笔记》 ,本专栏会专门记录博主在学习 JVM 中学习的知识点,以及遇到的问题。   🙉 文章详情: 本篇博客是学习 【狂神说Java】JVM快速入门篇 的学习笔记,关于 GC垃圾回收机制 (堆内存结

    2023年04月19日
    浏览(45)
  • [Lua] 探讨类与对象机制的实现、并实现抽象工厂模式

    Lua 没有严格的 oo(Object-Oriented)定义,可以利用元表特性来实现 先定义所有类的基类,即 Object 类。代码顺序从上到下,自成一体。完整代码 定义一个空表 Object , __index 指向其自身(继承将直接使用该表作为对象的元表) new 定义构造对象时的初始化行为,相当于 构造器

    2024年02月09日
    浏览(33)
  • [Lua][Love] 打砖块游戏实现过程与知识点

    本文旨在根据LOVE2D官方文档和教程实现打砖块的游戏,记录部分实现过程和重要知识点 目标摧毁所有砖块 玩家控制球拍左右滑动反弹小球 小球摧毁砖块 小球保持在屏幕内 小球碰到屏幕底部,GAME OVER 在加载引擎的时候回调该函数修改引擎基本参数,默认参数可看Config Files

    2024年02月11日
    浏览(43)
  • 【Lua学习笔记】Lua入门

    (不是教程,推荐有编程基础的人观看本文) 文中主要包含了对菜鸟教程中的一些学习理解,个人感觉Lua语言和Python很多地方相似 以下大部分代码和表格摘抄自菜鸟教程 数据类型 描述 nil 只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)(类似与Null或者

    2024年02月15日
    浏览(37)
  • [Lua][Love Engine] 打砖块游戏实现过程与知识点

    本文旨在根据LOVE2D官方文档和教程实现打砖块的游戏,记录部分实现过程和重要知识点 目标摧毁所有砖块 玩家控制球拍左右滑动反弹小球 小球摧毁砖块 小球保持在屏幕内 小球碰到屏幕底部,GAME OVER 在加载引擎的时候回调该函数修改引擎基本参数,默认参数可看Config Files

    2024年02月12日
    浏览(35)
  • 【Lua学习笔记】Lua进阶——协程

    协程是一种并发操作,相比于线程,线程在执行时往往是并行的,并且线程在创建销毁执行时极其消耗资源,并且过长的执行时间会造成主进程阻塞。而协程可以以并发时轮值时间片来执行,优点是不会阻塞,消耗资源少,可以手动控制。至于协程和线程的区别,什么是并发

    2024年02月15日
    浏览(94)
  • 【Lua学习笔记】Lua进阶——函数和闭包

    使用函数嵌套的用法,我们可以将另一个函数作为返回值,但是返回函数作为一个值是要被赋值给其他变量的,所以return时不能起名(赋值)为其他变量名。 推荐阅读深入Lua:函数和闭包 在函数嵌套中,我们需要接触一个叫做闭包的概念 这就是一个闭包,它由一个函数和该

    2024年02月15日
    浏览(56)
  • 【Lua学习笔记】Lua进阶——Table(3) 元表

    接上文 Q:为什么要使用元表? A:在Lua中,常常会需要表与表之间的操作。元表中提供了一些元方法,通过自定义元方法可以实现想要的功能,相当于面向对象中给你一系列方法让你重载。 使用下列代码设置元表: 通常在元表中操作分为三步: 操作子表 检测是否有元表 若

    2024年02月15日
    浏览(37)
  • 【Lua学习笔记】Lua进阶——Require,三目运算

    这是文件 aaa.lua 的内容 这是文件 example.lua 的内容 可以看到,在使用require之后,会直接对其他文件进行调用执行。而且我们可以直接访问它的全局变量,并且发现我们的全局变量被覆盖了,而它的局部变量就像private一样,不能被这个文件访问。 从 package.loaded 这个方法我们可

    2024年02月15日
    浏览(51)
  • 【Lua学习笔记】Lua进阶——Table(4)继承,封装,多态

    现在我们可以像面向对象一样,new一个对应基类的对象了。但是这里的new也不完全相似与面向对象的new,例如我们可以这样做: 我们在封装Object类的时候可完全没有name这个索引,而在Lua中我们new了一个新对象,还能新加入一些变量和方法,这些特性明显是继承了父类的子类才

    2024年02月15日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包