xlua源码分析(五) struct类型优化
xlua源码分析(五) struct类型优化
上一节我们分析了xlua是如何实现lua层访问C#值类型的,其中我们重点提到了xlua默认实现方式下,struct访问的效率问题。实际上,xlua还提供了两种优化的方式,可以大大提高struct访问的性能。具体例子在Examples 12_ReImplementInLua中。
第一种优化方式就是在lua层改造C#的struct,C# struct push到lua层时仍为userdata,但它的metatable不指向C#层struct,而是lua层自己实现的:
function test_vector3(title, v1, v2)print(title)v1.x = 100print(v1.x, v1.y, v1.z)print(v1, v2)print(v1 + v2)v1:Set(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)print(v1)print(CS.UnityEngine.Vector3.Normalize(v1))
endlocal get_x, set_x = xlua.genaccessor(0, 8)
local get_y, set_y = xlua.genaccessor(4, 8)
local get_z, set_z = xlua.genaccessor(8, 8)local fields_getters = {x = get_x, y = get_y, z = get_z
}
local fields_setters = {x = set_x, y = set_y, z = set_z
}local ins_methods = {Set = function(o, x, y, z)set_x(o, x)set_y(o, y)set_z(o, z)end
}local mt = {__index = function(o, k)--print('__index', k)if ins_methods[k] then return ins_methods[k] endreturn fields_getters[k] and fields_getters[k](o)end,__newindex = function(o, k, v)if fields_setters[k] then fields_setters[k](o, v) else error('no such field ' .. k) endend,__tostring = function(o)return string.format('vector3 { %f, %f, %f}', o.x, o.y, o.z)end,__add = function(a, b)return CS.UnityEngine.Vector3(a.x + b.x, a.y + b.y, a.z + b.z)end
}xlua.setmetatable(CS.UnityEngine.Vector3, mt)
test_vector3('----after change metatable----', CS.UnityEngine.Vector3(1, 2, 3), CS.UnityEngine.Vector3(7, 8, 9))
这里的代码,就是在lua层实现了一下Vector3的get/set属性和方法,然后替换掉原先的metatable,xlua.setmetatable就是做这个工作的,替换的逻辑很简单,就是找到要替换类的type id,重新设置到registry表里:
public static int XLuaMetatableOperation(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);Type type = getType(L, translator, 1);if (type == null){return LuaAPI.luaL_error(L, "xlua.metatable_operation, can not find c# type");}bool is_first = false;int type_id = translator.getTypeId(L, type, out is_first);var param_num = LuaAPI.lua_gettop(L);if (param_num == 1) //get{LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);return 1;}else if (param_num == 2) //set{if (LuaAPI.lua_type(L, 2) != LuaTypes.LUA_TTABLE){return LuaAPI.luaL_error(L, "argument #2 must be a table");}LuaAPI.lua_pushnumber(L, type_id);LuaAPI.xlua_rawseti(L, 2, 1);LuaAPI.xlua_rawseti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);return 0;}else{return LuaAPI.luaL_error(L, "invalid argument num for xlua.metatable_operation: " + param_num);}}catch (Exception e){return LuaAPI.luaL_error(L, "c# exception in xlua.metatable_operation: " + e);}
}
不过,lua层的Vector3依旧是userdata,如何在lua层对userdata设置/获取数据呢?为此,xlua提供了xlua.genaccessor函数,它接受两个参数,第一个参数表示要设置/获取的字段相对于struct的内存偏移,第二个参数表示要设置/获取的字段类型,对于Vector3,x,y,z的偏移分别为0,4,8,而它们的类型均为float,float在xlua预先定义的类型ID为8:
#define T_INT8 0
#define T_UINT8 1
#define T_INT16 2
#define T_UINT16 3
#define T_INT32 4
#define T_UINT32 5
#define T_INT64 6
#define T_UINT64 7
#define T_FLOAT 8
#define T_DOUBLE 9
genaccessor函数是在C层实现的,那其实很简单了,就是把userdata作为要访问内存的首地址,加上偏移量offset,执行memcpy即可,如果是get,就是从userdata拷贝到value,再push到lua栈;如果是set,就先从lua栈上取出value,再拷贝到userdata。
#define DIRECT_ACCESS(type, push_func, to_func) \
int xlua_struct_get_##type(lua_State *L) {\CSharpStruct *css = (CSharpStruct *)lua_touserdata(L, 1);\int offset = xlua_tointeger(L, lua_upvalueindex(1));\type val;\if (css == NULL || css->fake_id != -1 || css->len < offset + sizeof(type)) {\return luaL_error(L, "invalid c# struct!");\} else {\memcpy(&val, (&(css->data[0]) + offset), sizeof(type));\push_func(L, val);\return 1;\}\
}\
\
int xlua_struct_set_##type(lua_State *L) { \CSharpStruct *css = (CSharpStruct *)lua_touserdata(L, 1);\int offset = xlua_tointeger(L, lua_upvalueindex(1));\type val;\if (css == NULL || css->fake_id != -1 || css->len < offset + sizeof(type)) {\return luaL_error(L, "invalid c# struct!");\} else {\val = (type)to_func(L, 2);\memcpy((&(css->data[0]) + offset), &val, sizeof(type));\return 0;\}\
}\
上面例子的运行结果如下:

第二种优化方式,是将struct映射成table,即C#层push到lua层的struct,不再为userdata,而是一个table,xlua提供了PackAsTable这个attribute指示生成代码时采用映射table的方式:
[GCOptimize(OptimizeFlag.PackAsTable)]
public struct PushAsTableStruct
{public int x;public int y;
}
然后,lua层也需要实现配套的代码,即struct的object metatable和class metatable,相当于在lua层实现struct:
local mt = {__index = {SwapXY = function(o) --成员函数o.x, o.y = o.y, o.xend},__tostring = function(o) --打印格式化函数return string.format('struct { %d, %d}', o.x, o.y)end,
}xlua.setmetatable(CS.XLuaTest.PushAsTableStruct, mt)local PushAsTableStruct = {Print = function(o) --静态函数print(o.x, o.y)end
}setmetatable(PushAsTableStruct, {__call = function(_, x, y) --构造函数return setmetatable({x = x, y = y}, mt)end
})xlua.setclass(CS.XLuaTest, 'PushAsTableStruct', PushAsTableStruct)
在测试代码中,我们先在C#层push一下struct:
PushAsTableStruct test;
test.x = 100;
test.y = 200;
luaenv.Global.Set("from_cs", test);
然后再在lua层进行测试:
print('--------------from csharp---------------------')
assert(type(from_cs) == 'table')
print(from_cs)
CS.XLuaTest.PushAsTableStruct.Print(from_cs)
from_cs:SwapXY()
print(from_cs)print('--------------from lua---------------------')
local from_lua = CS.XLuaTest.PushAsTableStruct(4, 5)
assert(type(from_lua) == 'table')
print(from_lua)
CS.XLuaTest.PushAsTableStruct.Print(from_lua)
from_lua:SwapXY()
print(from_lua)
此时C#层push时,不会再生成userdata,而是生成一个table,然后设置字段x和字段y:
public void PushXLuaTestPushAsTableStruct(RealStatePtr L, XLuaTest.PushAsTableStruct val)
{if (XLuaTestPushAsTableStruct_TypeID == -1){bool is_first;XLuaTestPushAsTableStruct_TypeID = getTypeId(L, typeof(XLuaTest.PushAsTableStruct), out is_first);}LuaAPI.xlua_pushcstable(L, 2, XLuaTestPushAsTableStruct_TypeID);LuaAPI.xlua_pushasciistring(L, "x");LuaAPI.xlua_pushinteger(L, val.x);LuaAPI.lua_rawset(L, -3);LuaAPI.xlua_pushasciistring(L, "y");LuaAPI.xlua_pushinteger(L, val.y);LuaAPI.lua_rawset(L, -3);}
同样的道理,要从lua层把struct传递到C#层,就要获取lua层的table,把它的字段x和字段y取出,依次赋值到C#对象上:
public static void UnPack(ObjectTranslator translator, RealStatePtr L, int idx, out XLuaTest.PushAsTableStruct val)
{val = new XLuaTest.PushAsTableStruct();int top = LuaAPI.lua_gettop(L);if (Utils.LoadField(L, idx, "x")){translator.Get(L, top + 1, out val.x);}LuaAPI.lua_pop(L, 1);if (Utils.LoadField(L, idx, "y")){translator.Get(L, top + 1, out val.y);}LuaAPI.lua_pop(L, 1);}
例子的输出结果如下:

这两种优化方式,各有优劣,第一种方式,userdata比table更加省内存;而第二种方式,使用原始table操作性能上要比使用userdata要好。两种方式都需要额外生成一些代码。与tolua相比,tolua的struct是采用了类似第二种的方式,tolua的struct在lua层就是个table,需要完整按照C#层实现一遍struct。而数据传输的逻辑,稍微不太相同,tolua是使用lua函数进行数据传输,例如Vector3,tolua可以通过一个get函数直接返回3个float*给C#层,也可以通过一个new函数直接使用x,y,z三个参数构造出一个lua层的struct,pack和unpack的逻辑都放在了lua层里。
function Vector3.New(x, y, z) local t = {x = x or 0, y = y or 0, z = z or 0}setmetatable(t, Vector3) return t
endfunction Vector3.Get(v) return v.x, v.y, v.z
end
相关文章:
xlua源码分析(五) struct类型优化
xlua源码分析(五) struct类型优化 上一节我们分析了xlua是如何实现lua层访问C#值类型的,其中我们重点提到了xlua默认实现方式下,struct访问的效率问题。实际上,xlua还提供了两种优化的方式,可以大大提高str…...
iptables TEE模块测试小记
概述 因为公司项目需求,需要对服务器特定端口进行流量镜像,各种百度之后,发现TEE的模块,后来一番折腾,发现被转发的机器死活收不到数据,最后tcpdump一通了解到根源,博文记录,用以备…...
facebook广告怎么设置受众人群
在设置Facebook广告受众人群时,你可以遵循以下步骤: 打开广告创建工具,点击页面右上角的箭头并选择“创建广告”。选择广告目标,根据想要实现的目标创建广告。例如,想要让更多用户谈论你的主页和帖子,或者…...
MySQL夯实之路-MVCC机制深入浅出
多版本并发控制(MVCC,multiversion concurrency control) MVCC用更加灵活的方式处理并发,实现了读不加锁,读写不冲突。保证了事务的隔离性(可重复读),避免了不可重复读问题。 数据…...
Java线上问题堆栈排查分析
最近线上出现类似内存溢出问题,需要排查具体原因,记录过程,方便备查。 一、数据抓取 在启动参数中添加参数,可参照以下设置。 参数的作用是在程序发生内存溢出 OutOfMemory 时打印日志,dump下来,方便用工…...
C语言代码 计算1!+2!+3!+4!+5!+6!+7!+8!+9!+10!
计算1!2!3!4!5!6!7!8!9!10! 代码示例: #include <stdio.h> int main() {int i 0;int n 0;int ret 1;int sum 0;for (n 1; n < 10; n){ret 1;for (i 1; i < n; i){ret ret * i;}sum sum ret;}printf("%d\n", sum);return 0; } 运…...
【RTOS】快速体验FreeRTOS所有常用API(4)队列
目录 四、队列2.1 概念2.2 创建队列2.3 写队列2.4 读队列2.5 队列集(可跳过) 四、队列 该部分在上份代码基础上修改得来,代码下载链接: https://wwzr.lanzout.com/iBNAS1l75bvc 密码:7xy2 该代码尽量做到最简,不添加多…...
【开题报告】基于SpringBoot的美食制作学习网站的设计设计与实现
1.选题背景 随着人们生活水平的提高,对美食的追求也越来越高。越来越多的人希望能够在家里制作出各种美味的菜肴。然而,对于许多人来说,缺乏专业的指导和实践经验是一个挑战。另外,互联网的普及与发展,为人们提供了更…...
Rosalind Java|Speeding Up Motif Finding
Rosalind编程问题之计算错误矩阵(failure array)输出前后缀检索匹配。 Speeding Up Motif Finding Problem: A prefix of a length n string s is a substring s[1:j]; a suffix of s is a substring s[k:n]. The failure array of s is a…...
打印的前后顺序
面试题经常会有 <script>console.log(1)setTimeout(function(){console.log(2)})console.log(3)let pnew Promise((resolve,reject) >{console.log(4)resloved(hhhhhh)})p.then(res >{console.log(res)console.log(5)},res >{console.log(7)})console.log(6)&l…...
Android Retrofit使用详情
一、 Retrofit是什么 Retrofit是Android用来接口请求的网络框架,内部是基于OkHttp实现的,retrofit负责接口请求的封装,retrofit可以直接将接口数据解析为Bean类、List集合等,直接简化了中间繁琐的数据解析过程 二、 Retrofit的简单…...
安全加密算法
常用加密算法 对称加密 加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。常用对称加密算法如下: DES:密钥长度8个字节,安全性不足,已被证明…...
软件测试|使用matplotlib绘制多种饼图
简介 Matplotlib是一个强大的数据可视化库,它允许我们创建各种类型的图表,包括饼图。饼图是一种用于显示数据分布的常见图表类型。在本文中,我们将介绍如何使用Matplotlib创建不同类型的饼图,并提供示例代码。 创建标准饼图 首…...
vue3-响应式基础之ref
声明响应式状态 ref() 在组合式 API 中,推荐使用 ref() 函数来声明响应式状态: ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回: import { ref } from vue const count ref(0)console.log(count) // { va…...
华为网络设备 通过路由器子接口 Dot1q终结子接口实现跨VLAN通信
(二层交换机直接跳过三层交换价接入路由器时才使用该配置。推荐使用三层交换机建立VLANIF配置更简洁明了。如果VLAN较少可直接配置;路由器接口,一个物理接口一个VLAN) S1配置 vlan batch 2 to 3interface GigabitEthernet0/0/1port link-type trunkpor…...
代码随想录算法训练48 | 动态规划part09
今天就是打家劫舍的一天,这个系列不算难,大家可以一口气拿下。 198.打家劫舍 视频讲解:动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili 代码随想录 213.打家劫舍II 视频讲解&am…...
2024最新适用于 Windows 、Mac 的最佳屏幕录制软件
屏幕录制软件可以帮助我们录制 PC 和MacBook的实时屏幕视频。如果您想为 优酷录制视频,或者您正在为您的公司制作基于视频的项目,并且需要捕获屏幕的实时视频录制,那么我们在此列出了 一 款适合您的 Windows 、Mac的 2024 年最佳屏幕录制软件…...
【Docker】概述与安装
🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一. Docker的概述 1.Docker为什么出现 2…...
衡水学院新人真题百练2022(1-20)修订版
1 重要的话说三遍 分数 5 作者 陈越 单位 浙江大学 这道超级简单的题目没有任何输入。 你只需要把这句很重要的话 —— “I’m gonna WIN!”——连续输出三遍就可以了。 注意每遍占一行,除了每行的回车不能有任何多余字符。 #include<stdio.h> int…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
