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

推荐订阅源

S
Schneier on Security
F
Fortinet All Blogs
B
Blog
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
The Register - Security
The Register - Security
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
B
Blog RSS Feed
WordPress大学
WordPress大学
Recorded Future
Recorded Future
Recent Announcements
Recent Announcements
V
Vulnerabilities – Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Secure Thoughts
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Webroot Blog
Webroot Blog
AWS News Blog
AWS News Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
The GitHub Blog
The GitHub Blog
爱范儿
爱范儿
O
OpenAI News
月光博客
月光博客
H
Hacker News: Front Page
S
Security Affairs
W
WeLiveSecurity
The Hacker News
The Hacker News
aimingoo的专栏
aimingoo的专栏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Help Net Security
Help Net Security
MongoDB | Blog
MongoDB | Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
T
The Blog of Author Tim Ferriss
Spread Privacy
Spread Privacy
Blog — PlanetScale
Blog — PlanetScale
J
Java Code Geeks
S
Securelist
Microsoft Azure Blog
Microsoft Azure Blog
TaoSecurity Blog
TaoSecurity Blog
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
A
About on SuperTechFans

重归混沌的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 地形渲染之爬过的坑 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发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源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
Lua5.3 GC源码阅读(5)
重归混沌 · 2020-11-29 · via 重归混沌的BLOG

这篇内容并不是上篇提到的关于弱表实现的分析。而是最近有位同学与我探讨了一些Lua的GC时,发现了两个以前从没注意过的智慧闪光点。

首先是luaC_fullgc的实现.


 #define keepinvariant(g)	((g)->gcstate <= GCSatomic)

static void entersweep (lua_State *L) {
  global_State *g = G(L);
  g->gcstate = GCSswpallgc;
  lua_assert(g->sweepgc == NULL);
  g->sweepgc = sweeplist(L, &g->allgc, 1);
}

void luaC_fullgc (lua_State *L, int isemergency) {
  global_State *g = G(L);
  lua_assert(g->gckind == KGC_NORMAL);
  if (isemergency) g->gckind = KGC_EMERGENCY;  /* set flag */
  if (keepinvariant(g)) {  /* black objects? */
    entersweep(L); /* sweep everything to turn them back to white */
  }
  /* finish any pending sweep phase to start a new cycle */
  luaC_runtilstate(L, bitmask(GCSpause));
  luaC_runtilstate(L, ~bitmask(GCSpause));  /* start new collection */
  luaC_runtilstate(L, bitmask(GCScallfin));  /* run up to finalizers */
  /* estimate must be correct after a full GC cycle */
  lua_assert(g->GCestimate == gettotalbytes(g));
  luaC_runtilstate(L, bitmask(GCSpause));  /* finish collection */
  g->gckind = KGC_NORMAL;
  setpause(g);
}

在luaC_fullgc中,主要的意图是首先尽可能快的结束执行到一半的GC循环,然后从头开始一轮新的GC循环,以达到fullgc的语义。

那么怎么快速结束当前未结束的循环呢,Lua采用了一个很有智慧的方法,那就是快速进入sweep阶段(即如果当前还没有处理sweep阶段,就直接进入sweep阶段)。那么我为什么说这是一个很有智慧的方式呢,这是因为不论在整个GC状态机的任意一个状态进入sweep阶段,整个GC状态都不会出错,这是我以前没有意识到的。

先来看一下整个GC状态机全部状态:

 #define GCSpropagate	0
 #define GCSatomic	1
 #define GCSswpallgc	2
 #define GCSswpfinobj	3
 #define GCSswptobefnz	4
 #define GCSswpend	5
 #define GCScallfin	6
 #define GCSpause	7

如果将一个GC循环按照标记和清除来区分,很显然GC循环的开始在GCSpropagate, 结束在GCSpause。整个GC循环被GCSatomic状态分为标记和清除两个大阶段。

为了更清楚的看清整个GC循环,我们将每个GC循环概括为GCMark和GCSweep两个阶段,并把几个GC循环连起来看。

GCMark1, GCSweep1, GCMark2, GCSweep2,GCMark3, GCSweep3…

由于atomic函数会改变当前GC循环的白色值,因此GCSweep1,和GCMakr2阶段拥有相同的白色值,而GCSweep2,GCMark3拥有相同的白色值。

从sweeplist函数可以看出GCSweep2只会清理GCSweep1和GCMark2阶段的白色对象。并把所有存活对象都变成当前GC循环的白色值。

假如我们在执行GCMark2过程中调用了luaC_fullgc导致跳过了atomic阶段的执行,那么就不会产生更换GC循环白色值的行为,这样GCSweep1,GCMark2,GCSweep2,GCMark3都使用了相同的白色值来执行标记清除逻辑。由于GCSweep1已经清除过GCSweep0和GCMark1阶段产生(包括由黑变白)的白色对象,那么GCSweep2的作用其实只剩下“把所有在GCMark2阶段标记过的对象都变白”。相当于撤销了GCMark1的作用。而从GCMark3开始就能执行一轮全新的GC循环。

这就变相达到了最快结束当前GC循环的目的。


另一个就是luaC_barrierback的实现.

 
 #define luaC_barrierback(L,p,v) (  \
	(iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \
	luaC_barrierback_(L,p) : cast_void(0))

void luaC_barrierback_ (lua_State *L, Table *t) {
  global_State *g = G(L);
  lua_assert(isblack(t) && !isdead(g, t));
  black2gray(t);  /* make table gray (again) */
  linkgclist(t, g->grayagain);
}

这个函数其实在上篇分析中也有提到过。但那是在GCMark阶段时分析的luaC_barrierback的作用。我从来没有注意过如果在GCSweep阶段,触发了luaC_barrierback会怎么样。

在GCSweep阶段会触发luaC_barrierback么?答案是肯定的。因为sweeplist是增量执行的,不可能一下子就把所有黑对象变白。如果这时操作黑对象,就会触发luaC_barrierback。

那么在GCSweep阶段,如果触发了luaCbarrierback,那么相关黑对象会被变成灰对象,然后被挂在g->grayagain链表上。同时如果一个对象触发了luaC_barrierback, 说明他还没有对sweeplist处理过,也就是说最终他一定会被sweeplist函数变成白色。

这时就会存在一种情况,一个对象被挂在g->grayagain链表上,但是它是白色。这就是其中的智慧闪光点,虽然一个白色对象挂在g->grayagain, 但是他不会有副作用。因为在restartcollection函数中的 g->gray = g->grayagain = NULL语句会将整个grayagain链表清空。同时虽然白色对象的gclist字段依然有无效值,但是在下次插入gray或grayagain之前,会对此对象的gclist字段重新赋值,因此也不会有任何副作用。

纵观这两个例子可以发现,sweeplist本来是为了GCSatomic阶段之后清除对象而生,而luaC_barrierback本来也是为了在GCMark阶段保持GC循环不变式而生。但是他们在这两个场景之外,依然有良好的适用场景。这得益于良好的抽象,我有时甚至都在想,这也许已经是人类智慧的极限了。