lua/c开发:只读数据共享方案
这里只讨论单一进程内的只读数据共享。
同一进程内虚拟内存空间是原本就共享的(以C为例),但在业务开发上,一般会嵌入脚本语言,使用VM的沙盒环境独立维护不同的上下文(以lua为例),多个VM之间(暂时称为业务VM)的数据相互独立。
业务上涉及数据共享的,一般的场景是优化性能、资源占用的情况。需要共享的一般是业务上不变的配置数据或函数原型,每个VM单独维护会带来较大内存浪费、增加gc压力。这里讨论在lua VM之间共享数据的方案。
显然的思路,在业务VM之外单独开启一块内存维护共享的数据,所有业务VM请求获取该数据。
维护这块共享内存,应该要支持:
- 线程安全
- 不可修改,支持热更新
- 优化内存消耗
- 访问尽可能高效
这里根据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逻辑使共享表脱离局部虚拟机的管理。
本文来自博客园,作者:linxx-,转载请注明原文链接:https://www.cnblogs.com/linxx-/p/18091075