lua_gc 源码学习三

我们晓得,lua 对外的 API 中,统统个 gc 打交道的都经过lua_gc。C 说话构建体系时,普通不讲计划模式。但模式仍是存在的。若要按《计划模式》中的分类,这应当归于 Facade 形式。代码在 lapi.c 的 895 行:

 LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State *g; lua_lock(L); g = G(L); switch (what) { case LUA_GCSTOP: g->GCthreshold = MAX_LUMEM; break; case LUA_GCRESTART: g->GCthreshold = g->totalbytes; break; case LUA_GCCOLLECT: luaC_fullgc(L); break; case LUA_GCCOUNT: res = cast_int(g->totalbytes >> 10); break; case LUA_GCCOUNTB: res = cast_int(g->totalbytes & 0x3ff); break; case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) res = 1; break; } break; } case LUA_GCSETPAUSE: res = g->gcpause; g->gcpause = data; break; case LUA_GCSETSTEPMUL: res = g->gcstepmul; g->gcstepmul = data; break; default: res = -1; } lua_unlock(L); return res; }

从代码可见,对核心状态的会见,都是干脆接见 global state 表的。GC 控制则是调用内部 api 。lua 中对外的 api 和内部模块交互的 api 都是分隔的。这样井井有条。内部子模块平常名为luaX_xxxX 为子模块代号。对收集器相干的 api 一概以luaC_xxx定名。这些 api 定义在 lgc.h 中。

其间提到的 api 有两个:

LUAI_FUNC void luaC_step (lua_State *L); LUAI_FUNC void luaC_fullgc (lua_State *L);

用于分步 GC 已经完备 GC 。

另外一个主要的 api 是:

#define luaC_checkGC(L) \ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ if (G(L)->totalbytes >= G(L)->GCthreshold) \ luaC_step(L);

它以宏情势定义进去,用于主动的 GC 。如果我们检查 lapi.c ldo.c lvm.c ,会发明大部门会导致内存增加的 api 中,都挪用了它。包管 gc 可以随内存利用增长而主动停止。

这里插几句。

使用自动 gc 会有一个题目。它极可能使体系的峰值内存占用远超过实际需要量。缘由就在于,收集行动每每产生在调用栈很深之处。当你的利用法式显现出某种周期性(大大都包驱动的办事都是这样)。在一个办事周期内,常常会援用浩繁姑且对象,这个时辰做 mark 工作,会导致很多且则对象也被 mark 住。

一个阅历方式是,调用LUA_GCSTOP遏制自动 GC。在周时代按期调用 gcstep 且使用较大的 data 值,在无限个周期做完一整趟 gc 。

另,condhardstacktests 是一个宏,一般为不打开的。

先来看luaC_fullgc。它用来执行完全的一次 gc 行动。fullgc 并非只是把当前的流程走完。由于以前的 gc 行动可能执行了一半,大概有一些半途加出去的需要收受接管的对象。所以在走完一趟流程后,fullgc 将梗阻着再完好跑一遍 gc 。整个流程有一些优化的余地。即,前半程的 gc 流程并不必严厉执行,它其实不需要真的去断根什么。只要要把状态规复。这个工作是怎样做到的呢?见 lgc.c 的 637 行:

void luaC_fullgc (lua_State *L) { global_State *g = G(L); if (g->gcstate <= GCSpropagate) g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->gcstate = GCSsweepstring; lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate); while (g->gcstate != GCSfinalize) lua_assert(g->gcstate == GCSsweepstring

比力耗时的 mark 步调被简略跳过了(如果它还没举行完的话)。和一般的 mark 流程分歧,平常的 mark 流程末了,会将红色标识表记标帜反转。见 lgc.c 548 行,atomic 函数:

 g->currentwhite = cast_byte(otherwhite(g));

在 fullgc 的前半程中,间接跳过了 GCSpropagate ,重置了外部状况,但没有翻转白色标志。这会导致背面的 sweep 流程不会真的开释那些白色工具。sweep 工作现实做的仅仅把一切工具又从新设置回白色罢了。

接下来便是一个完好不被打断的 gc 历程了。

markroot(L); while (g->gcstate != GCSpause) singlestep(L); setthreshold(g);

从根起头 mark ,直到全部 gc 流程履行终了。最终,从头配置了 GCthreshold 。注:调用 fullgc 会重置 GCthreshold ,以是如果你曾挪用LUA_GCSTOP停息自动 GC 的话(也是经由过程点窜 GCthreshold 完成),记得再调用一次。

stepgc 要相对于庞大一些。在 lua 手册的 2.10 诠释了 garbage-collector pause 和 step multiplier 的意思,却不给出切确界说。lua_gc的申明里,也只说“LUA_GCSTEP: 倡议一步增量渣滓收集。步数由 data 控制(越大的值象征着越多步), 而其详细寄义(具体数字暗示了几多)并未尺度化。假如你想控制这个步数,必需尝试性的测试 data 的值。 要是这一步完成了一个废物收集周期,返回返回 1 。并无给出精确的寄义。理论中,我们也都因此经历取值。

回到源代码,我们就可以搞清晰它们究竟是甚么了。

case LUA_GCSETPAUSE: res = g->gcpause; g->gcpause = data; break; case LUA_GCSETSTEPMUL: res = g->gcstepmul; g->gcstepmul = data; break;

这里只是设置 gcpause gcstepmul 。gcpause 实际只在 lgc.c 59 行的 setthreshold 宏顶用到

#define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause)

瞥见,GCSETPAUSE 实际上是通过调剂 GCthreshold 来实现的。当 GCthreshold 充足大时,luaC_step不会被luaC_checkGC自动触发。究竟上,GCSTOP 恰是通过设置一个很大的 GCthreshold 值来实现的。

case LUA_GCSTOP: g->GCthreshold = MAX_LUMEM; break;

gcpause 值的含义很文档分歧,用来示意和现实内存利用量 estimate 的比值(扩大 100 倍)。一旦内存使用量超出这个阀值,就会动身 GC 的工作。

要明白 gcstepmul ,就要从lua_gc的LUA_GCSTEP的实现看起。

case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) res = 1; break; } break; }

step 的长度 data 被扩大了 1024 倍。在 lgc.c 的 26 行,也能够看到

#define GCSTEPSIZE 1024u

咱们权且能够以为 data 的单元是 KBytes ,和 lua 统共占用的内存 totalbytes 有些干系。

ps. 这里 totalbytes 是严格通过 Alloc 办理的内存量,比来。而后面提到的 estimate 则差别,它是一个预算量,比 totalbytes 要小。这是因为,前方也提到过,userdata 的接纳对照特别。被检测出已经拜候不到的 userdata 占用的内存并不会顿时开释(担保 gc 元要领的平安调用),但 estimate 会抛去这部份,不算在实际内存使用量内。

见 lgc.c 544 行

udsize = luaC_separateudata(L, 0);

和 lgc.c 553 行

g->estimate = g->totalbytes - udsize;

从代码逻辑,咱们临时可以把 data 了解为,必要处置惩罚的字节数目(以 K bytes 为单元)。若是需求处置的数据量跨越了 totalbytes ,天然就能够把 GCthreshold 配置为 0 了。

实际上不克不及完整这么明白。因为 GC 过程并不是一点点回收内存,同时可用内存愈来愈多。GC 分符号(mark) 肃清(sweep) 调用 userdata 元方法等几个阶段。只要中心的排除阶段是真实释放内存的。所以可用内存的增加( totalbytes 削减)过程,时候上并不是线性的。每每标志的开消更大。为了让 gcstep 的每一个程序耗损的时间更光滑,就得有手腕静态调解 GCthreshold 值。它和 totalbytes 终究影响了每一个 step 的时间。

上面的存眷核心转向luaC_step ,见 lgc.c 的 611 行:

void luaC_step (lua_State *L) { global_State *g = G(L); l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; if (lim == 0) lim = (MAX_LUMEM-1)/2; g->gcdept += g->totalbytes - g->GCthreshold; do lim -= singlestep(L); if (g->gcstate == GCSpause) break; while (lim > 0); if (g->gcstate != GCSpause) { if (g->gcdept < GCSTEPSIZE) g->GCthreshold = g->totalbytes + GCSTEPSIZE; else g->gcdept -= GCSTEPSIZE; g->GCthreshold = g->totalbytes; } else lua_assert(g->totalbytes >= g->estimate); setthreshold(g); }

从代码我们可以看到,GC 的焦点其其实于 singlestep 函数。luaC_step屡次调用几何次 singlestep 跟 gcstepmul 的值相关。

如果是自动进行的 GC ,当 totalbytes 大于即是 GCthreshold 时,就会触发luaC_step。每次luaC_step,GCthreshold 城市被调高 1K (GCSTEPSIZE) 直到 GCthreshold 追上 totalbytes 。这个追逐进程凡是产生在 mark 流程。由于这个流程中,totalbytes 是只增不减的。

如果是手控 GC ,屡次 gcstep 调用履行几许次luaC_step则跟 data 值相关。大致上是 1 就默示一次(在 mark 过程当中便是如许)到了 sweep 流程就未必了。这和 singlestep 调用次数,即 gcstepmul 的值有关。它影响了 totalbytes 的减小速度。

以是,一两句话很难严厉定义出这些控制 GC 步进量的参数的含义,只能渐渐浏览代码,看看实现了。

在 lua 手册的 2.10 如许描写“step multiplier 节制了收集器相对于内存分派的速率。更大的数字将致使收集器事情的更自动的同时,也使每步搜集的尺寸增添。 小于 1 的值会使收集器劳动的很是慢,大概导致搜集器永久都竣事不了以后周期。 缺省值为 2 ,这象征着收集器将之内存分派器的两倍速运转。”

从代码看,这绝非严酷界说。最少从本日已阐发的代码中还看不出这一点。

时间: 2024-08-16 16:52:17

lua_gc 源码学习三的相关文章

[spring源码学习]三、IOC源码——自定义配置文件读取

一.环境准备 在文件读取的时候,第9步我们发现spring会根据标签的namespace来选择读取方式,联想spring里提供的各种标签,比如<aop:xxx>等应该会有不同的读取和解析方式,这一章我们来找一个其他文件,了解下spring自定义标签和配置的读取流程. 手边正好有一套dubbo的源码,因此为了区别与spring的原生读取,就使用它来进行分析. 首先spring的配置文件中我们需要加上标签的namespace <?xml version="1.0" enc

Bottle 框架源码学习 三

def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,         interval=1, reloader=False, quiet=False, plugins=None,         debug=None, **kargs): 今天要学习一下bottle里是怎样打印debug信息的 run函数的倒数第二个参数是debug,默认为None try:     if debug is not None: _debu

lua_gc 源码学习四

今天来看一下 mark 过程是怎样实现的. 所有的 GC 流程,都从 singlestep 函数开始.singlestep 就是一个最简单的状态机.GC 状态简单的从一个状态切换到下一个状态,循环不止.状态标识放在 global state 的 gcstate 域中.这一点前面谈过. 开始的两个状态和 mark 过程有关. 初始的 GCSpause 状态下,执行 markroot 函数.我们来看一下 markroot 的代码.见 lgc.c 的 501 行. /* mark root set *

lua_gc 源码学习二

普及下常识:GC 是 garbage collector 资源回收器: 初期的 Lua GC 采取的是 stop the world 的实现.一旦产生 gc 就需要期待全部 gc 流程走完.lua 自己是个很精简的体系,但不代表处理的数据量也必然很小. 从 Lua 5.1 入手下手,GC 的实现改成分步的.固然照旧是 stop the world ,可是,每个步骤均可以分阶段执行.这样,屡次搁浅的时间较小.随之,这部门的代码也相对于纷乱了.分步执行最关键的问题是需要处理在 GC 的步骤之间,如果

Nmap 源码学习三 nmap_main主程序分析

主体程序位置在nmap.cc line:1640 学习要点: 程序在1650行,新建一个主机的单例对象, #ifndef NOLUA /* Only NSE scripts can add targets */ NewTargets *new_targets = NULL; /* Pre-Scan and Post-Scan script results datastructure */ ScriptResults *script_scan_results = NULL; #endif 从168

Nmap 源码学习三 软件简单使用

软件安装环境是win7.使用Zenmap, nmap6.49BETA2 扫描主机端口 nmap -T4 -A -v 192.168.0.207 输出结果: 扫描整个子网 nmap 192.168.1.1/24 扫描多个目标: nmap 192.168.1.2 192.168.1.5 从文件加载ip列表扫描 nmap -iL target.txt 查看扫描的主机列表 nmap -sL 192.168.1.1/24 扫描特定端口 nmap -p80,21,8080 192.168.0.207 半开扫

lua_gc 源码学习五

今天来说说 write barrier . 在 GC 的扫描过程中,由于分步执行,难免会出现少描了一半时,那些已经被置黑的对象又被修改,需要重新标记的情况.这就需要在改写对象时,建立 write barrier .在扫描过程中触发 write barrier 的操作影响的对象被正确染色,或是把需要再染色的对象记录下来,留到 mark 的最后阶段 atomic 完成. 和 barrier 相关的 API 有四个,定义在 lgc.h 86 行: #define luaC_barrier(L,p,v)

lua_gc 源码学习六

GC 中最繁杂的 mark 部分已经谈完了.剩下的东西很简单.今天一次可以写完. sweep 分两个步骤,一个是清理字符串,另一个是清理其它对象.看代码,lgc.c 573 行: case GCSsweepstring: { lu_mem old = g->totalbytes; sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]); if (g->sweepstrgc >= g->strt.size) /* not

Spring源码学习(三)默认标签的解析

默认标签的解析分为四种:import,alias,bean,beans,在下面函数中进行 1 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 2 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { 3 importBeanDefinitionResource(ele); 4 } 5 else if (delegate.n