普及下常识:GC 是 garbage collector 资源回收器;

初期的 Lua GC 采取的是 stop the world 的实现。一旦产生 gc 就需要期待全部 gc 流程走完。lua 自己是个很精简的体系,但不代表处理的数据量也必然很小。

从 Lua 5.1 入手下手,GC 的实现改成分步的。固然照旧是 stop the world ,可是,每个步骤均可以分阶段执行。这样,屡次搁浅的时间较小。随之,这部门的代码也相对于纷乱了。分步执行最关键的问题是需要处理在 GC 的步骤之间,如果数据关联的状态发生了变化,如何保证 GC 的准确性。GC 的分步执行相对一次执行完,总的时候开销的不同并非零代价的。只是在实现上,要尽可能让额外增长的价钱较小。

先来看 GC 流程的分别。

lua 的 GC 分为五个大的阶段GC 处于哪一个阶段(代码中被称为状态),根据的是global_State中的 gcstate 域。状态以宏式样界说在 lgc.h 的 14 行。

/*
** Possible states of the Garbage Collector
*/
#define GCSpause    0
#define GCSpropagate    1
#define GCSsweepstring  2
#define GCSsweep    3
#define GCSfinalize 4

状态的值的巨细也表示着它们的履行顺序。需要注重的是,GC 的执行历程并不是每程序都堵塞在一个状况上。

GCSpause 阶段是每一个 GC 流程的启始步调。仅仅标识表记标帜体系的根节点。见 lgc.c 的 561 行。

switch (g->gcstate) 
{
case GCSpause: markroot(L);  /* start a new collection */
return 0;

markroot 这个函数所做之事,就是标记主线程对象,标记主线程的局部表、注册表,以及为局部范例注册的元表。标记的详细过程我们背面再讲。

GCSpause 阶段执行完,立即就将状态切换到了 GCSpropagate 。这是一个标记流程。这个流程会分步结束。当检测到另有对象待标记时,迭代标记(重复调用 propagatemark);终究,会有一个标记功程不可被打断,这些操纵放在一个叫作 atomic 的函数中执行。见 lgc.c 的 565 行:

case GCSpropagate: 
{
if (g->gray)
return propagatemark(g);
else /* no more `gray' objects */
atomic(L);  /* finish mark phase */
return 0;
}

这里大概需要顺带提一下的是 gray 域。望文生义,它指 GCObject 中的灰色节点链。作为灰色,即处于白色和黑色之间的状态。对于节点的颜色,顿时就会阐明。

接下来就是清除流程了。

前面我们提到过,string 在 Lua 中是单独经管的,以是也需要单独清除。GCSsweepstring 阶段干的就是这个事。string table 以 hash 表形式治理所有的 string 。GCSsweepstring 中,每个步骤(step) 清理 hash 表的一列。代码见 lgc.c 的 573 行

case GCSsweepstring: lu_mem old = g->totalbytes;
sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);
if (g->sweepstrgc >= g->strt.size)  /* nothing more to sweep? */
g->gcstate = GCSsweep;  /* end sweep-string phase */
lua_assert(old >= g->totalbytes);
g->estimate -= old - g->totalbytes;
return GCSWEEPCOST;

这里可以看到 estimate 和 totalbytes 两个域,从名字上可以知道,它们别离暗示了 lua vm 占用的内存字节数以及实际分派的字节数。

ps. 如果你本身实现过内存管理器,当知道内存治理自己是有分外的内存开销的。如果有需要确切控制内存数目,我偏向于连续内存管理器统计精确的内存使用环境。比如你向内存管理器索取 8 字节内存,实际的内存开销极可能是 12 字节,乃至更多。如果想做这方面的点窜,让 lua 的 gc 能更真正的反映内存实际使用情形,保举修改 lmem.c 的 76 行,luaM_realloc_函数。所有的 lua 中的内存使用改变城市经由过程这个函数。

从下面这段代码中,我们还见到了 GCSWEEPCOST 这个秘密数字。这个数字用于节制 GC 的进度。这超越了本日的话题。留待今后阐发。

接下来就是对所有未标记的别的 GCObject 做清理劳动了。即 GCSsweep 阶段。它和上面的 GCSsweepstring 近似。

最终是 GCSfinalize 阶段。假如在前方的阶段发现了需要调用 gc 元方法的 userdata 对象,将在这个阶段逐一调用。做这件工作的函数是 GCTM

后面已谈到过,全部具有 gc 元要领的 userdata 对象和其联系关系的数据,现实上都不会在以前的断根阶段被肃清。(因为零丁做过标志)所有的元方法挪用都是平安的。而它们的现实排除,则需比及下一次 GC 流程了。或是在lua_close被调历时消灭。

ps.lua_close其实不做完全的 gc 事情,只是简单的处置惩罚所有 userdata 的 gc 元方法,以及开释所有效到的内存。它是相对于便宜的。

接下来我们看看 GC 标记流程触及的一些观点。

简略的说,lua 以为每一个 GCObject (需要被 GC 搜集器办理的工具)都有一个色彩。一起头,一切节点都是白色的。新建立进去的节点也被默许配置为红色。

在标记阶段,可见的节点,逐个被设置为玄色。有些节点比较庞大,它会联系关系此外节点。再不处置完所相关联节点前,lua 以为它是灰色的。

节点的颜色被贮存在 GCObject 的 CommonHeader 里,放在 marked 域中。为了节俭内存,因此用位寄存。marked 是一个单字节量。统共可以储存 8 个标记。而 lua 5.1.4 用到了 7 个标记位。在 lgc.h 的 41 行,有其诠释:

/*
** Layout for bit use in `marked' field:
** bit 0 - object is white (type 0)
** bit 1 - object is white (type 1)
** bit 2 - object is black
** bit 3 - for userdata: has been finalized
** bit 3 - for tables: has weak keys
** bit 4 - for tables: has weak values
** bit 5 - object is fixed (should not be collected)
** bit 6 - object is "super" fixed (only the main thread)
*/
#define WHITE0BIT   0
#define WHITE1BIT   1
#define BLACKBIT    2
#define FINALIZEDBIT    3
#define KEYWEAKBIT  3
#define VALUEWEAKBIT    4
#define FIXEDBIT    5
#define SFIXEDBIT   6
#define WHITEBITS   bit2mask(WHITE0BIT, WHITE1BIT)

lua 定义了一组宏来操纵这些标记位,代码就再也不列出。只要要翻开 lgc.h 就可以很轻快的明白这些宏的函数。

白色和黑色是划分标记的。当一个对象非白非黑时,就认为它是灰色的。

为何有两个白色符号位?这是 lua 采取的一个小技能。在 GC 的标志流程完成,但清算流程还没有作完前。一旦对象间的干系产生转变,好比新增添了一个对象。这些对象的生命期是不能预知的。最安定的方法是把它们标记为不可消除的。由于清理进程结束,需要把所有对象设置回白色,便利下次清算。lua 其实是单遍扫描,处置完一个节点就重置一个节点的颜色的。简朴的把新创建出来的对象设置为黑,有可能致使它在 GC 流程竣事后,再也没机遇变回白色了。

简略的方法便是设置从第三种状态。也便是第 2 种白色。

在 Lua 中,两个白色状态是一个乒乓开关,当前需要删除 0 型白色节点时, 1 型白色节点就是被保护起来的;反之也同样。

以后的白色是 0 型仍是 1 型,见global_State的 currentwhite 域。otherwhite() 用于乒乓切换。获适当前白色状态,利用定义在 lgcc.h 77 行的宏:

#define luaC_white(g)   cast(lu_byte, (g)->currentwhite & WHITEBITS)

FINALIZEDBIT 用于标记 userdata 。当 userdata 确定不被援用,则设置上这个标记。它分歧于颜色标记。因为 userdata 由于 gc 元方法的存在,释放所占内存是需要放到 gc 元方法调用以后的。这个标记可以包管元方法不会被频频调用。

KEYWEAKBIT 和 VALUEWEAKBIT 用于标记 table 的 weak 属性,无需多言。

FIXEDBIT 可以保证一个 GCObject 不会在 GC 流程中被清除。为什么要有这类状态?关键在于 lua 本身会用到一个字符串,它们有可能不被任何处所引用,但在屡次用到这个字符串时。那么,这些字符串就会被珍爱起来,设置上 FIXEDBIT ,Mac下主动杀死ssh历程并重连

在 lstring.h 的 24 行界说有:

#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT)

能够把一个字符串配置为被庇护的。

典范的运用场所见 llex.c 的 64 行:

void luaX_init (lua_State *L) {
int i;
for (i=0; i<NUM_RESERVED; i++) 
TString *ts="luaS_new(L," luaX_tokens[i]); 
luaS_fix(ts); 
reserved words are never collected * lua_assert(strlen(luaX_tokens[i])+1 <="TOKEN_LEN);" ts->tsv.reserved = cast_byte(i+1);  /* reserved word */
}

以及 ltm.c 的 30 行:

void luaT_init (lua_State *L) {
static const char *const luaT_eventname[] = /* ORDER TM */
"__index", "__newindex",
"__gc", "__mode", "__eq",
"__add", "__sub", "__mul", "__div", "__mod",
"__pow", "__unm", "__len", "__lt", "__le",
"__concat", "__call";
int i;
for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]);
luaS_fix(G(L)->tmname[i]);  /* never collect these names */
}

以元方式为例,若是咱们操纵 lua 尺度 api 来摹拟 metatable 的行动,就不能写的和原生的 meta 机制高效。由于,当我们取到一个 table 的 key ,想知道它是否是__index时,要末我们需要挪用 strcmp 做比力;要么利用lua_pushlstring先将需要比较的 string 压入lua_State,而后再比较。

咱们晓得 lua 中值分歧的 string 同享了一个 string 工具,即 TString 地点是一概的。对照两个 lua string 的价格是很小(只需求比力一个指针),比 C 函数 strcmp 高效。但lua_pushlstring却有额外开消。它必要去计较 hash 值,盘问 hash 表 (string table) 。

lua 的 GC 算法并不做内存清算,它不会在内存中迁徙数据。实际上,如果你能确定一个 string 不会被清除,那末它的内存地点也是稳定的,这样就带来的优化空间。ltm.c 中就是如许做的。

见 lstate.c 的 93 行:

TString *tmname[TM_N];  /* array with tag-method names */

global_State中 tmname 域就干脆以 TString 指针的体例记实了所有元方法的名字。换作准则的 lua api 来做的话,凡是我们需要把这些 string 放到注册表,或情况表中,才干担保其不被 gc 清除,且可以在比较时拿到。lua 本身的完成则哄骗 FIXEDBIT 做了一步优化。

最后,我们来看看 SFIXEDBIT 。实在它的用处只要一个,就是标记主 mainthread 。也就是一概的出发点。我们调用lua_newstate返回的阿谁布局。

为甚么需要把这个布局特别看待?因为即便到lua_close的那一刻,这个构造也是不克随便清除的。我们来看看天下末日时,法式都履行了什么?见 lstate.c 的 105 行。

static void close_state (lua_State *L) global_State *g = G(L);
luaF_close(L, L->stack);  /* close all upvalues for this thread */
luaC_freeall(L);  /* collect all objects */
lua_assert(g->rootgc == obj2gco(L));
lua_assert(g->strt.nuse == 0);
luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size, TString *);
luaZ_freebuffer(L, &g->buff);
freestack(L, L);
lua_assert(g->totalbytes == sizeof(LG));
(*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0);

这是lua_close的最后一个步骤。luaC_freeall将释放所有的 GCObject ,但不包罗有 SFIXEDBIT 的 mainthread 对象 。见 lgc.c 484 行

void luaC_freeall (lua_State *L) bitmask(SFIXEDBIT);  /* mask to collect all elements */
sweepwholelist(L, &g->rootgc);
for (i = 0; i < g->strt.size; i++)  /* free all string lists */
sweepwholelist(L, &g->strt.hash[i]);

这里 FIXEDBIT 是被疏忽的,而在此以前,FIXEDBIT 被护卫着。见 lstate.c 的 153 行(lua_newstate函数):

g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);

这么做很轻易了解,lua 天下的发源,统统根数据都放在这个对象中,要是被提早清理,背面的代码就会出题目。真实释放这个对象不是在 GC 中,而是末了那句:

lua_assert(g->totalbytes == sizeof(LG));
(*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0);

顺带还 assert 了一下,最后,世界上是不是只剩下这个结构。

lua_gc 源码学习二相关推荐

  1. Golang源码学习(二)----Go源码学习基础

    ### 本文源码版本为 GO 1.17.8 Windows/amd64: ### 可能参与对比的版本:GO 1.16.2 Linux/amd64一.Golang的编译器究竟是如何工作的? (学习源码有 ...

  2. SocketServer源码学习(二)

    SocketServer 中非常重要的两个基类就是:BaseServer 和 BaseRequestHandler 在SocketServer 中也提供了对TCP以及UDP的高级封装,这次我们主要通过 ...

  3. 使用base标签后图片无法加载_Spring 源码学习(二)-默认标签解析

    `Spring` 解析默认标签~ 从上一篇笔记可以看出,在容器注册 bean 信息的时候,做了很多解析操作,而 xml 文件中包含了很多标签.属性,例如 bean . import 标签, meta ...

  4. ROS源码学习 二、线程池

    2021SC@SDUSC 目录 1.写在前面 2.ROS线程池概述 3.ROS线程池模型 4.ROS线程池源码详解 5.总结 1.写在前面 ROS作为一个操作系统,其职责是协调具有不同功能的node之 ...

  5. Box2d源码学习二内存管理之SOA的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8258166 SOA,全称small object al ...

  6. java集合类程序代码_java集合类源码学习二

    我们查看Collection接口的hierarchy时候,可以看到AbstractCollection这样一个抽象类,它实现了Collection接口的部分方法,Collection集合系列的各个集合 ...

  7. Thrift源码学习二——Server层

    Thrift 提供了如图五种模式:TSimpleServer.TNonblockingServer.THsHaServer.TThreadPoolServer.TThreadSelectorServe ...

  8. erlang底层c定时器设计-Erlang源码学习二

    Erlang底层的定时器实现位于源码的erts/emulator/beam/time.c文件,用时间轮的方式动态添加和删除定时器,结构体名为typedef struct ErtsTimerWheel_ ...

  9. yara 源码学习(二) 规则编译部分

    yara规则的详细信息请参考: https://yara.readthedocs.io/en/stable/writingrules.html 根据官方文档,yara规则长这个样子: [1]:yara ...

最新文章

  1. python爬虫requests实战_Python_爬虫_requests小实战
  2. AI洞观 | 一文读懂英特尔的AI之路
  3. 心得丨8个深度学习方面的最佳实践
  4. 如何利用FineReport制作动态树报表
  5. 详解GPU的常见参数及其对显卡的重要性
  6. Debian 新维护人员手册
  7. 在表格中批量显示图片
  8. 企业微服务:实现crud操作之创建实体类
  9. 博弈论:别人的想法(文末送书!)
  10. 三天100元从零开始搭建Hadoop集群
  11. xutil3网络框架
  12. S3VM和TSVM的不同
  13. 蚁群算法(Ant Colony Optimization,ACO)介绍及MATLAB代码(求解旅行商问题TSP)
  14. 274-MySQL(查看源码的工具sourceinsight)
  15. batchplot 3.6.2 插件_插件分享 | 可在线查询子域名和同IP域名的RapidDNS
  16. apache ab linux 下载,linux 下ab压力测试
  17. 蚂蚁的愤怒之源(落日余晖)-终结篇
  18. 2020年6月——北京大学《软件工程》笔记
  19. 简单的视频剪辑入门制作软件,适合新手哦,快快收藏
  20. python多维数组分位数的求取

热门文章

  1. 数字体验监控:您需要知道的一切
  2. 互联网为何“杀死”中年人?人人皆笑余欢水,人人皆是余欢水!
  3. linux系统 JDK11安装
  4. 读书笔记-卷积神经网络
  5. 第2章 带宽负担会降低人们的智商
  6. 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include StdAfx.h”?
  7. uda1341声卡的linux 2.6.29版本内核驱动在arm s3c2440上的移植
  8. 【计算思维题】少儿编程 蓝桥杯青少组计算思维真题及详细解析第6套
  9. Sublime Text的命令行工具subl
  10. python三种方法实现阶乘