(Unity) 为被 Lua 隔断的 C# 实现添加 Profiler 支持

问题描述

Unity 项目在实践中往往选择使用 Lua 作为更上层的逻辑脚本。这一方面是由于 Unity 本身对热更不是很友好,用 Lua 热更灵活得多,另一方面也是简化与服务器共享代码和数据。目前多种不同的 Unity + Lua 集成方案中,实践中采用比较多的是庞巍伟同学的 slua 方案。

使用 Lua 的团队,往往倾向于“较重的”集成,也就是暴露相当大规模的引擎接口给 Lua,这样逻辑上才能有足够的自由度对游戏和引擎做全面的控制。当 C#/Lua 之间的互操作接口迅速增长到成千上万的数量时,一个重要的问题就会浮现出来:C# 和 Lua 的交互层对引擎是封闭的,很多引擎内建的工具,没有办法跨越宿主 (C#) 和脚本 (Lua) 的边界。这些受到影响的机制里,最重要的就是 Unity Profiler。

Unity Profiler 是 Unity 提供的一个有力的性能分析工具 ,能够在优化阶段有效地帮助定位瓶颈,有时也容易借机发现一些潜藏的 bug。而当我们定位到某个 Lua 函数有较大的开销(CPU 或内存 GC Alloc)时,由于跨语言边界的影响被阻拦,就难以进一步观察更多的细节。

“正常的”做法

由于 Profiler 的 API 接口也一并暴露给了脚本,正常的做法是:根据 C# 这边的有问题的调用,翻到对应的 Lua 代码,把相关的脚本逻辑读一遍,为那些潜在的开销大的逻辑添加对应的性能剖析采样 Profiler.BeginSample()/EndSample(),来定位问题代码段落,然后再翻回对应 C# 函数,再在里面加上测试代码印证我们的想法。

实际上我们知道,大多数情况下(如果不算 bug),与引擎部分相比,逻辑脚本的 CPU 开销是相对比较低的(逻辑代码里以 if 判断居多,遇到需要循环的情况都非常少,一般用不到啥非常复杂的运算——或者说在设计得当的情况下,复杂的运算都会交给底层去整块整块地做),而容易造成困扰的托管内存分配导致 GC 卡顿的内存问题,也是完全由脚本调回来的 C# 代码造成的。

这样分析下来,往往绕一圈又回到 C# 里,中间付出了大量无谓的在脚本里兜圈子的时间不说,被逼着读和分析重复性高的脚本逻辑代码,也大大增加了精力和脑力的负担。

AOP

既然知道了反正总是要从 Lua 回到 C# 的,那么有没有什么简单的办法,一劳永逸地为所有暴露给 Lua 的 C# 接口加上性能剖析采样呢?

如果能做到这一点,我们就可以无视中间脚本层 (Lua) 的干扰,在 C# 环境内解决所有问题。

俺的目光很自然地投向了 AOP (Aspect Oriented Programming),这种技术能帮我们在不用修改目标函数代码的情况下,加入我们想执行的代码(就像 Python 的 decorator 那样)。

python-deco

经过一番研究,我成功地得出以下这条结论:

现有的一些针对 C# 的 AOP 方法,在 Unity 的 mono 下,基本都跪了~~

还能不能让俺过一个快乐的儿童节了~~

在这些尝试里,最接近成功的是:使用 lambda expression 包一层,添加相关代码后,再注册给 slua,然而,slua 需要为注册进来的函数添加下面的 attribute:

    [MonoPInvokeCallbackAttribute (typeof (xxx))]
而 C# 不支持为 lambda expression 添加 attribute,所以 &_& ……

利用 slua 代码生成的简明做法

发现难以通过 C# 本身的语言机制解决问题之后,我把目光投向了 slua:既然所有的绑定代码是 slua 生成的,那么不如直接修改生成代码,把采样代码生成到接口的绑定函数里~

找到普通函数接口的生成位置

void WriteFunctionImpl( StreamWriter file, MethodInfo m, Type t, BindingFlags bf)
{
    ...
}
在一头一尾添加了对应的生成代码(BeginSample() 的参数可以直接用 MethodInfo.Name 得到正确的函数名 ),运行 slua 的 Make 生成一下,得到下面的结果(单个函数):

[MonoPInvokeCallbackAttribute(typeof(xxx))]
static public int xxx(IntPtr l) {
    try {
        Profiler. BeginSample( "xxx");

        ...

        return 1;
    }
    catch( Exception e) {
        return error( l, e);
    }
    finally {
        Profiler. EndSample();
    }
}
EndSample() 在 finally 内,保证每个出口都能正确配对。

粒度控制

接下来更进一步,我们希望有某种粒度的控制能力,只为某个关心的类生成,甚至只为该类内关心的函数生成。回到前面的函数生成所在的方法,可以看到签名:

void WriteFunctionImpl( StreamWriter file, MethodInfo m, Type t, BindingFlags bf);
其中第二个参数可以用来筛选我们关心的函数(可以跟 m.Name 比较来过滤字符串),而第三个参数 Type t 可以用来筛选对应的类(通过 if ( t == typeof(TargetClass))),这样就可以只在我们需要的时候,为特定的类和函数生成了。

时间: 2024-09-20 01:03:25

(Unity) 为被 Lua 隔断的 C# 实现添加 Profiler 支持的相关文章

(Unity) 为动态加载的 C# DLL 添加调试支持

昨天遇到了一个 C# DLL 动态载入后调试信息缺失的问题,今天上午解决后记录一下,以便遇到这个问题的同学可以参考. (注)此文中的截图内文字偏小,可以 Ctrl + 鼠标滚轮放大查看. 问题描述 我们知道,Unity 中的 Debug.Log() 系列函数不仅能输出用户内容,而且能通过类似 StackTraceUtility.ExtractStackTrace() 这样的机制把该输出对应的堆栈打出来:当用户代码出现未捕获异常时,Unity 也会利用该机制输出异常及相关的完整堆栈信息. 如这个函

Lua编程示例(五): C语言对Lua表的读取和添加_Lua

#include "stdafx.h" lua_State *L; void load_lua(char *filename){ L=luaL_newstate(); luaL_openlibs(L); if((luaL_loadfile(L,filename) || lua_pcall(L,0,0,0))!= 0){ luaL_error(L,"loadfile error! \n %s",lua_tostring(L,-1)); } } double getfi

EditPlus配置Lua运行环境

    (所需要的文件: http://pan.baidu.com/s/1mg7O7v6 )     首先安装Lua for Windows(如果安装过程无法连接服务器,可能需要手动安装Microsoft visual c++ 2005 sp1),然后打开EditPlus,选择Tools -> Configure User Tools进行设置: 正则内容设置为 ^.:\\.*: (.:\\.*):(\d+):.*     设置好后新建.lua文件,按Ctrl+1就可以对Lua程序进行编译运行了.

用lua_tinker将lua脚本嵌入到游戏服务器

      忙中偷闲,经过几天的努力,将lua脚本嵌入到系统中.之前公司的时候,偌大一个服务器全部使用C++编写,对于新手经常发生一些宕机事件,被主程责骂.在后来接触的一些人中,发现很多公,都已经引入lua来适应多变的环境和敏捷开发!正如一个主程所说的,在n年前网易已经脚本为王了,现在很多公司拿着C++不放,作为开发人员不苦逼才怪! 想想在广州开发游戏的日子,每次在群里面看到运维说某某服务器上面有coredump文件时,总是惊出冷汗,赶紧用gdb去查询,是哪行代码引起的宕机:还要应对主程的责骂!

Unity编译至Xcode工程后自动添加文件和库的方法

XUPorter项目Github链接 为什么想要自动添加 由于Unity是全平台的游戏开发环境,在开发中针对特定平台的特定功能时,很难避免根据对象平台的不同而引入不同的依赖.包括源码,需要的库和框架等.在使用各种插件后这种情况愈发严重:比如想加入内购功能,StroreKit.framework必不可少,而且也需要相应的处理代码.按照一般的Unity插件开发流程,在完成.cs的接口声明和Unity侧的调用实现后,最重要的当然是在iOS native侧完成实现.而在以前,包括依赖库和所有源码文件,都

notepad++ 查找引用(Find Reference)(适用于c c++及各类脚本比如lua、python等)

在程序开发过程中,程序员经常用到的一个功能就是查找引用(Find Reference),Visual Studio里面的对应功能是"查找所有引用"(Find All References).     我在使用notepad++写代码的时候一开始一直因为找不到类似的功能而苦恼.只好每次使用"在文件中查找"(Find in files)来找到所有引用.     但是这样每次查找都需要遍历目标目录的整个文件系统,文件多的话会耗费很长时间.     notepad++有插件

lua、groovy嵌入到java中的性能对比(转)

lua和groovy都是可以嵌入到java中的脚本语言.lua以高性能著称,与C/C++在游戏开放中有较多使用,groovy是一个基于Java虚拟机(JVM)的敏捷动态语言,在jvm下有着不错的性能. groovy天生与java有着极高的兼容性,两者间对象无缝存取,支持jsr223.而lua是基于C的,需要调用jni,jni的性能是硬伤.这块网上基本都用luajava,好多年不更新了,不支持jsr223,而且很多方法都没有实现,也不能做到对象无缝存取,比如lua传到java的对象,java用不了

《Unity着色器和屏幕特效》——第2章 创建自定义着色器 2.1 打开程序项目

第2章 创建自定义着色器 这一章的内容将继续围绕飞船维修场景展开.通过添加自定义着色器,场景的真实感将获得进一步提升.我们将探索物体表面与场景光照之间的互动关系,展示Unity的基于物理的着色系统为游戏画面的质量带来的提升. 本章涉及的内容如下: 从无到有创建基本的自定义着色器. 为自定义着色器添加属性. 在场景中测试自定义着色器. 使用Cg语言编写着色器代码. 用多遍渲染实现更好的透明效果. 创建一个自定义的大气着色器. 2.1 打开程序项目 如果读者还没有打开过本书附带的示例程序项目,请从前

【狂云歌之unity_vr】unity项目持续集成dailybuild以及多平台打包管理

[狂云歌之unity_vr]unity项目持续集成dailybuild以及多平台打包管理 前言  持续集成的意义就不多说了.unity通常打包一般就直接build&run,但是在实际项目中,往往直接在服务器build包,所以命令行打包必不可少,这里一方面分享unity打包做持续集成,一方面分享使用unity管理多平台打包,例如一个vrapp需要支持gear版本,支持小米版本,支持cardboard版本等等~懂的人就知道这里具有一定的管理维护成本.  我们做vr相关的app,需要支持gear.ca