lua/c开发:只读数据共享方案

这里只讨论单一进程内的只读数据共享。

同一进程内虚拟内存空间是原本就共享的(以C为例),但在业务开发上,一般会嵌入脚本语言,使用VM的沙盒环境独立维护不同的上下文(以lua为例),多个VM之间(暂时称为业务VM)的数据相互独立。

业务上涉及数据共享的,一般的场景是优化性能、资源占用的情况。需要共享的一般是业务上不变的配置数据或函数原型,每个VM单独维护会带来较大内存浪费、增加gc压力。这里讨论在lua VM之间共享数据的方案。

显然的思路,在业务VM之外单独开启一块内存维护共享的数据,所有业务VM请求获取该数据。

维护这块共享内存,应该要支持:

  1. 线程安全
  2. 不可修改,支持热更新
  3. 优化内存消耗
  4. 访问尽可能高效

这里根据lua的类型分成两部分讨论:函数原型 / table

一. 共享函数原型(lua类型:Proto)

对于lua而言,每个脚本文件load起来都是一个函数。需要解决的问题就是,如何让脚本文件只从IO中加载一次,被需要的VM共享,而不是加载到所有的VM中。

方案:函数原型中的字节码、常量表和调试信息中,对除了常量表和cache之外的部分进行共享。不可共享的部分继承原有的 proto 结构,再用一个指针指向共享部分。改写gc支持Proto类型的引用计数gc策略(实际上保留所有使用过的函数原型不做回收也可以),并由实际load起该函数原型的VM负责进行回收。改写lua的luaL_loadfilex API,使用codecache结构体存放加载的缓存。

二.共享配置数据(lua类型:table)

最简单的方案,将共享数据放在单独的VM中,业务VM访问该公共VM进行获取数据。但VM之间的数据交互相较C层明显高出非常多,性能上不满足。将共享数据存放在redis中的方案也是同理,并且访问redis还需要让出执行权,是异步动作,对进行一次读数据操作这显然是不可接受的。下面讨论几种可行的方案:

方案一:在C层生成唯一的lua table的C对象,全局共享。每个VM使用单独的代理对象进行访问。单次访问代理对象进行一次lua to c 的调用,通过c function 访问到c层的共享数据。

不足点:1)代理对象仍然存在于每个VM中,访问的数据量大时还是带来一定内存开销;
2)通过c function访问c对象,开销仍然比直接访问table大得多。

方案二:仍然在C层生成唯一的C对象,但是规范table中不存在Thread Function Userdata等类型,这样可以做到将Table指针直接返回到lua层使用,而不通过c function访问,需要额外支持gc逻辑使共享表脱离局部虚拟机的管理。

posted @ 2024-03-23 14:17  linxx-  阅读(26)  评论(0编辑  收藏  举报