惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
爱范儿
爱范儿
Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
B
Blog RSS Feed
博客园 - 聂微东
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
WordPress大学
WordPress大学
Scott Helme
Scott Helme
AI
AI
S
Security Affairs
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
T
Troy Hunt's Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
AWS News Blog
AWS News Blog
T
Threatpost
Cyberwarzone
Cyberwarzone
www.infosecurity-magazine.com
www.infosecurity-magazine.com
U
Unit 42
V
Vulnerabilities – Threatpost
J
Java Code Geeks
博客园 - Franky
月光博客
月光博客
Blog — PlanetScale
Blog — PlanetScale
NISL@THU
NISL@THU
D
Docker
小众软件
小众软件
N
News and Events Feed by Topic
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
A
Arctic Wolf
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
Forbes - Security
Forbes - Security
量子位
PCI Perspectives
PCI Perspectives
美团技术团队
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
I
InfoQ
Security Archives - TechRepublic
Security Archives - TechRepublic
有赞技术团队
有赞技术团队
腾讯CDC
P
Proofpoint News Feed
S
Security @ Cisco Blogs
G
Google Developers Blog
C
Cisco Blogs

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
Lua5.3 GC源码阅读(2)
重归混沌 · 2018-04-11 · via 重归混沌的BLOG

上篇已经提到表作为lua中的一种对象,其生命周期是被GC管理的.

下面就从一个表的创建来分析GC模块是如何接管其生命周期的.

先来看一段lua代码

--a.lua
local a = {}
a.foo = "bar"

使用`luac -p -l a.lua` 得到如下结果

main <a.lua:0,0> (3 instructions at 0x20d7a50)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
        1       [1]     NEWTABLE        0 0 0
        2       [2]     SETTABLE        0 -1 -2 ; "foo" "bar"
        3       [2]     RETURN          0 1

NEWTABLE对就OPCODE中的OP_NEWTABLE.

根据lopcodes.h中的定义, OP_NEWTABLE使用A,B,C三个寄存器,其中寄存器A用来接收创建的table, 寄存器B和C用于为新创建的table预分配array部分大小和hash部分大小.

在lvm.c中找到代码片段刚好印证了上面的解释.

vmcase(OP_NEWTABLE) {
  int b = GETARG_B(i);
  int c = GETARG_C(i);
  Table *t = luaH_new(L);
  sethvalue(L, ra, t);
  if (b != 0 || c != 0)
    luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c));
  checkGC(L, ra + 1);
  vmbreak;
}

从luaH_new的定义可以得到一个结论,所有需要GC管理的对象都会调用luaC_newobj函数来分配内存空间.

//ltable.c
Table *luaH_new (lua_State *L) {
  GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
  Table *t = gco2t(o);
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

在luaC_newobj函数中, 首先调用luaM_newobject来为对象分配内存, 然后为正确管理此对象生命周期做了一些初始化操作.
1. 将对象标记为白色(lua5.3中使用的是三色标记清除垃圾回收算法)
2. 为此对象标记类型(类型的含义与上篇tt_字段的类型含义相同)
3. 将其挂在global_State上的allgc链表上.

由此可以得出一个结论,所有需要GC管理生命周期的对象都在allgc的链表上

//lgc.c
Object *luaC_newobj (lua_State *L, int tt, size_t sz) {
  global_State *g = G(L);
  GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
  o->marked = luaC_white(g);
  o->tt = tt;
  o->next = g->allgc;
  g->allgc = o;
  return o;
}

在luaM_newobject中就只做了两件事:

1. 调用g->frealloc进行分配内存,如果分配失败的话,执一次fullgc,再次尝试分配
2. 将新增使用内存量, 加入到global_State中的GCdebt字段中.

其中g->frealloc是在调用lua_newstate创建lua_State时指定的, 值得一提的是当lua_Alloc中的参数ptr为null时, osize参数为要new的对象的类型.

//lua.h
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
//lmem.h
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
//lmem.c
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
   void *newblock;
   global_State *g = G(L);
   size_t realosize = (block) ? osize : 0;
   lua_assert((realosize == 0) == (block == NULL));
 #if defined(HARDMEMTESTS)
   if (nsize > realosize && g->gcrunning)
     luaC_fullgc(L, 1);  /* force a GC whenever possible */
 #endif
   newblock = (*g->frealloc)(g->ud, block, osize, nsize);
   if (newblock == NULL && nsize > 0) {
     lua_assert(nsize > realosize);  /* cannot fail when shrinking a block */
     if (g->version) {  /* is state fully built? */
       luaC_fullgc(L, 1);  /* try to free some memory... */
       newblock = (*g->frealloc)(g->ud, block, osize, nsize);  /* try again */
     }
     if (newblock == NULL)
       luaD_throw(L, LUA_ERRMEM);
   }
   lua_assert((nsize == 0) == (newblock == NULL));
   g->GCdebt = (g->GCdebt + nsize) - realosize;
   return newblock;
}

OP_NEWTABLE的最后一步就是调用checkGC, 如果有未偿还的债务,就执行一次luaC_step.

//lvm.c
#define checkGC(L,c)  \
  { luaC_condGC(L, L->top = (c),  /* limit of live values */ \
		   Protect(L->top = ci->top));  /* restore top */ \
     luai_threadyield(L); }
//lgc.h
 #define luaC_condGC(L,pre,pos) \
         { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \
           condchangemem(L,pre,pos); }

Lua中的GC实现为’增量标记-清除回收器’, 每次调用luaC_step都会执行一次增加回收.

到目前为止, GC可以通过luaC_newobj和luaC_condGC来获得哪些对象需要被其管理生命周期, 并且什么时间触发增量GC操作.

后面开始分析luaC_step函数.