C#跟Lua如何超高性能传递数据
前言
在UWA学堂上线那天,我买了招文勇这篇Lua交互的课程,19块还算值,但是前段时间太忙,一直没空研究,他的demo是基于xlua的,今天终于花了大半天时间在tolua下跑起来了,记录一下我的理解

性能,仍然是Lua中与C#混用的大坑
Lua跟C#交互的性能问题是老生常谈的了,c#跟lua数据交互是通过lua虚拟栈,进行压栈、出栈来传递的,一次调用就需要执行很多指令,性能会随着调用次数的频繁,函数参数的增多而变差。直接操作内存的方式,可以在c#端修改lua内存,省去了操作虚拟栈,函数调用的大把指令,性能也就很高效了
腾讯的UnLua(给虚幻4用的)中也有类似的直接操作内存的交互方式,看来这种方式会渐渐成为主流,毕竟性能摆在这呢

Lua跟C#高效共享大量数据的一种方法
原理其实很简单,在c#端定义好lua table的结构体,必须在内存中对齐lua端的table,然后在c#端拿到lua table的指针,读写这块内存,就能读写这个lua table了。
是不是觉得非常简单,哈哈哈哈。感觉自己马上就能弄出来了
想要实现这套东西,还得搞懂几个问题,下面开始一一讲解
Lua Table结构体是什么样的?
想在c#端写一个lua table结构体,那就先看看lua端这个结构体是怎么实现的吧。在tolua下,我们使用的是luajit,jit的源码跟lua是不一样的,luajit又分32位跟64位。所以我们这个table结构体也需要做多套才行
luajit中的GCTab就是Table的结构体了
typedef struct GCtab {
GCHeader;
uint8_t nomm; /* Negative cache for fast metamethods. */
int8_t colo; /* Array colocation. */
MRef array; /* Array part. */
GCRef gclist;
GCRef metatable; /* Must be at same offset in GCudata. */
MRef node; /* Hash part. */
uint32_t asize; /* Size of array part (keys [0, asize-1]). */
uint32_t hmask; /* Hash part mask (size of hash part - 1). */
#if LJ_GC64
MRef freetop; /* Top of free elements. */
#endif
} GCtab;
GCHeader是每一个GC对象都要包含的一个宏,定义了这些属性
#define GCHeader GCRef nextgc; uint8_t marked; uint8_t gct
lua的table支持数组、哈希表两种用法,甚至可以同时是数组又是哈希表。我们主要处理数组的数据交互,结构体中的MRef array;就是这个table的所有数据存储的地方了,而asize就等于这个数组的长度+1。所以我们重点关注这2个字段的内存地址
如何设计c#端的table结构体呢?
我们把GCTab结构体展开成这样看
GCRef nextgc;
uint8_t marked; uint8_t gct; uint8_t nomm; int8_t colo;
MRef array;
GCRef gclist;
GCRef metatable;
MRef node;
uint32_t asize;
uint32_t hmask;
MRef freetop;//这个是64位的才会有
GCRef 跟 MRef 都是一个jit中封装的指针类型,会自动根据宏展开为32位跟64位。
GCRef 表示这是一个GC对象的指针
MRef 表示非GC对象的内存指针在c#中都可以用IntPtr类型代替
uint8_t 是8字节的,我们把4个8字节的放在一起,可以用一个int32位占用
那么转换到c#中,结构体就变成了这样
// GC64 version
public struct LuaJitGCtabGC64
{
IntPtr nextgc;
UInt32 masks;
IntPtr array;
IntPtr gclist;
IntPtr metatable;
IntPtr node;
UInt32 asize;
UInt32 hmask;
IntPtr freetop; // only valid for LJ_GC64
}
指针array指向的数据是什么?
在lj_tab.c中看tab的实现,我们很快就能找到array里存的是TValue结构,TValue其实是一个联合体。
联合体是多个结构体可以共享同一块内存,访问的时候可以用不同的结构体方式去访问。具体什么是联合体可以自行百度哦
TValue源码
/* Tagged value. */
typedef LJ_ALIGN(8) union TValue {
uint64_t u64; /* 64 bit pattern overlaps number. */
lua_Number n; /* Number object overlaps split tag/value object. */
#if LJ_GC64
GCRef gcr; /* GCobj reference with tag. */
int64_t it64;
struct {
LJ_ENDIAN_LOHI(
int32_t i; /* Integer value. */
, uint32_t it; /* Internal object tag. Must overlap MSW of number. */
)
};
#else
struct {
LJ_ENDIAN_LOHI(
union {
GCRef gcr; /* GCobj reference (if any). */
int32_t i; /* Integer value. */
};
, uint32_t it; /* Internal object tag. Must overlap MSW of number. */
)
};
#endif
#if LJ_FR2
int64_t ftsz; /* Frame type and size of previous frame, or PC. */
#else
struct {
LJ_ENDIAN_LOHI(
GCRef func; /* Function for next frame (or dummy L). */
, FrameLink tp; /* Link to previous frame. */
)
} fr;
#endif
struct {
LJ_ENDIAN_LOHI(
uint32_t lo; /* Lower 32 bits of number. */
, uint32_t hi; /* Upper 32 bits of number. */
)
} u32;
} TValue;
这中间有很多宏,看着很乱,但其实我们只需要用2种模式就行了,因为我们只实现int跟double。作者给出的方式是如下这种
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct LuaJitTValue
{
// uint64
[FieldOffset(0)]
public UInt64 u64;
// number
[FieldOffset(0)]
public double n;
// integer value
[FieldOffset(0)]
public int i;
// internal object tag for GC64
[FieldOffset(0)]
public Int64 it64;
// internal object tag
[FieldOffset(4)]
public UInt32 it;
}
但这里我有一些我还没弄明白,因为我实际运行起来后,不管lua赋值的是整形,还是浮点,int i始终没有值,值都存在了double n中。那为啥作者要弄一个int i跟UInt32 it; 这个it还偏移了4字节
在c#端我们可以使用[StructLayout(LayoutKind.Explicit)]和[FieldOffset(0)]来实现c语言中的联合体,具体方式可以看这篇文章
https://blog.csdn.net/wonengxing/article/details/44302661
如何用unsafe模式读写结构体?
结构体都定义好了,接下来我们看看怎么读写一个double
LuaJitGCtab32* TableRawPtr; //需要拿到Lua端Table的指针
//赋值操作
TableRawPtr->array[index].n = val;
//取值操作
TableRawPtr->array[index].n;
没错,就是这么简单。直接就可以操作lua内存了。
如何拿到lua端table的指针?
在lua端传入一个table参数过来,我们可以在c#端操作虚拟栈转成指针
System.IntPtr arg0 = LuaDLL.lua_topointer(L, 1);
看到这里,相信大部分的谜团都已经解开了,真的自己可以实现一套出来了。
总结
作者提供的方案里,只支持int、double。只支持array类型的table。还有luajit64位貌似没支持好。所以如果真正要使用的话,还要改很多东西
C#跟Lua如何超高性能传递数据的更多相关文章
- 【Lua】LWT后台用JSON与 ExtJS传递数据
要完成目录树的构建,需要前台ExtJS构筑页面,后台处理逻辑,中间由JSON传递数据. 首先搭建后台环境: require "httpd" require "lfs&qu ...
- 【openresty】向lua代码中传递参数
前面介绍FormInputNginxModule模块时,明白了openresty如何获取post提交的数据. 然后,如果需要通过lua处理这些数据,需要把数据作为参数传递到lua中,lua获取了这些数 ...
- (转)如何在JavaScript与ActiveX之间传递数据3
本文研究如何在JS等脚本语言与ActiveX控件之间通信,如何传递各种类型的参数,以及COM的IDispatch接口.使用类似的方法,可以推广到其他所有脚本型语言,如LUA,AutoCad等.本文将研 ...
- Android中Service通信(一)——启动Service并传递数据
启动Service并传递数据的小实例(通过外界与服务进行通信): 1.activity_main.xml: <EditText android:layout_width="match_ ...
- angular input标签只能单向传递数据的问题
angularjs input标签只能单向传递数据的问题 <ion-view title = "{{roomName}}" style = "height:90%; ...
- activity与fragment之间传递数据
总结:无论是activity给fragment传递数据,还是fragment给activity传递数据,都把activity和fragment都当做一个普通的对象,调用它的方法,传递参数. 1.Fra ...
- android跟服务器使用json传递数据
最近在做项目,使用了json传递数据,把服务器对象转换成json字符串返回,android使用gson包解析json字符串变成对象. 1.服务器代码编写,我这边是在servlet里面 Peron pe ...
- 小菜学习Winform(五)窗体间传递数据
前言 做项目的时候,winfrom因为没有B/S的缓存机制,窗体间传递数据没有B/S页面传递数据那么方便,今天我们就说下winfrom中窗体传值的几种方式. 共有字段传递 共有字段传递实现起来很方便, ...
- Intent(三)向下一个活动传递数据
向下传递活动很简单,可以我采用putExtra()方法的重载,把我们想要传递的数据暂时放在intent中,启动活动时从这里取就可以了. 首先我们在MainActivity(主活动)显式声明intent ...
随机推荐
- win10应用开发——如何判断应用是在手机上运行还是电脑上运行
原文:win10应用开发--如何判断应用是在手机上运行还是电脑上运行 在进行uwp应用开发的时候, 有时我们需要知道自己的应用是在手机端运行还是在桌面端运行,那么通过以下的api就可以进行判断: Wi ...
- SQLite的使用(包括编译安装的步骤)
SQLite官网http://www.sqlite.org/ SQLite简介 SQLite是一款轻型的数据库,是遵守ACID(原子性.一致性.隔离性和持久性)的关系式数据库管理系统.SQLite实现 ...
- 多态与虚拟 : 物件导向的精髓 (侯捷在石器时代对OO的理解)
[自序]虑而後能得(自序)故事接触 C++ 大约是 1989 年的事.那时候的 PC 以现在的眼光看,除了「蛮荒」之外没有更合适的形容词了.横扫千军的 Windows 3.0 还没有诞生,如今以 C+ ...
- 获取其他进程中“Internet Explorer_TridentCmboBx”的内容
function GetTridentCmboBxText( // 获取其他进程中“Internet Explorer_TridentCmboBx”的内容 mHandle: THandle; // ...
- 任何一件事,如果你不投入时间和精力去驯养,就不可能产生真正的兴趣和热爱(Focus Feedback FixIt的原理) good
这两本书和我们说的兴趣结合起来,为我们指明了精进的道路: 选择一个你感兴趣的方向 刻意练习 持续投入时间和精力 所谓刻意练习,简单说就是“3F”,即: Focus Feedback Fix it Fo ...
- 深入理解Java虚拟机(字节码执行引擎)
深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...
- Django 的路由系统
Django 的路由系统 Django 的路由系统 路由层 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$',views.ho ...
- (Demo分享)利用原生JavaScript-ScrollLeft-实现做轮播广告通知
轮播广告通知整体思路: 1.首先文字的移动利用了JAVA script 中ScrollLeft的知识点: 2.在设置一条一模一样的新闻,利用无缝轮播图滚动的原理让新闻无缝滚动. 3.使用了自执行匿名函 ...
- 系统学习 Java IO (四)----文件的读写和随机访问 FileInputStream/FileOutputStream & RandomAccessFile
目录:系统学习 Java IO---- 目录,概览 文件输入流 FileInputStream 这是一个简单的FileInputStream示例: InputStream input = new Fi ...
- 微信开发:微信js_sdk 分享,前端部分(二)
微信开发:微信js-sdk前端分享,代码如下: <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"> ...