ILRuntime:用寄存器模式吊打Lua
前言
ILRunTime虽然有很多优点,其中有一点在以前经常被拿来与Lua进行比较,就是ILRunTime的数值计算性能
由于Lua采用了寄存器模式,而ILRunTime在过去是没有支持寄存器模式的,所以在比较时都会说ILRunTime的计算性能比起Lua要略微差一点
但是现在ILRunTime已经增加了寄存器模式,接下来我们就来看一看什么是ILRunTime的寄存器模式以及这种模式解决了哪些问题?有哪些优点?
版权声明
本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
详细内容可观看文末完整视频
ILRunTime寄存器介绍

上图是ILRunTime官网中关于寄存器模式的科普
寄存器模式是ILRunTime2.0版引入的专用于优化大规模数值计算的执行模式,该模式通过ILRunTime自己的JITCompiler将原始的DLL指令集转换成一个自定义的基于寄存器的指令集,再进行解译执行
由于该JIT编译的结果是ILRunTime自己设计的虚拟指令集,并不是真实硬件指令集,因此可以毫无问题的在IOS等平台上执行
寄存器模式的定义说的通俗一点,就是寄存器模式在原来的由VS生成的DLL程序集的基础上,在程序执行之前,利用JIT(即时编译)工具对指令集进行优化,可以把原来很多需要在内存里运算的东西放到寄存器里进行运算
寄存器说白了就是CPU里的一块缓存区域,要衡量一个CPU的性能指标,重点就是看它的缓存有多少,缓存的访问速度高于内存的访问速度又高于磁盘的访问速度,所以使用缓存(寄存器)方式的性能是高于在内存上进行数据读写的方式
寄存器模式的开启方法
开启寄存器有两种方法
第一种方法:写一条语句(如下方代码)
这条语句在你创建ILRunTIme的应用程序域时会指定一个JITOnDemand标志,这个标签能够使寄存器模式在热更程序及层面上整体生效,是一种全局模式
第二种方法是在一些特定的要经常进行高性能数学运算的类或方法上开启寄存器模式
比如在上面代码中的foo类上加上一个标志ILRunTimeJIT,就表示在这个类上采用JIT模式进行编译,编译后得到的代码就是基于寄存器模式的,这个类中的方法也都会采用JIT模式
如果想让这个类中的一些特定方法不使用JIT,可以给这个方法加上[ILRunTimeJIT(ILRunTimeJITFlags.NoJIT)]标志
LIRunTime的几种JIT模式

JITOnDemand模式
按需JIT模式,使用该模式在默认的情况下会按照原始方式运行,当该方法被反复执行时,会被标记为需要被JIT,并在后台线程完成JIT编译后切换到寄存器模式运行
在ILRunTime解释器插件内部有一个计数器,用来统计方法被执行的频率和次数,如果频率比较高,它就会为这个方法开启寄存器模式
JITImmediately模式
立即JIT模式,使用该模式时,当方法被调用的瞬间即会被执行JIT编译,在第一次执行时即使用寄存器模式运行。JIT会在当前线程发生,因此如果方法过于复杂在第一次执行时可能会有较大的初始化时间
为方法标记上JITImmediately后,就不会去对这个方法的执行频率进行分析,会立刻被编译成寄存器模式的代码
由于编码过程发生在当前执行方法的线程上,如果这个方法的内容很长,那么它的JIT编译是有时间开销的,所以如果使用了JITImmediately模式,那么在第一次执行这个方法时,编译开销会使程序在当前线程上卡一会儿,所以这里要想办法对经常要使用的一些方法进行预热
NoJIT模式
禁用JIT模式,该方法在执行时会始终以传统方式执行
ForceInIine模式
强制内联模式,该模式只对方法的Attribute生效,标注该模式的方法在被调用时将会无视方法体内容大小,强制被内联
寄存器模式的性能特点

当运行在寄存器模式时,主要会有以下性能特征:
数值计算性能会大幅提升,包括for循环等需要数值计算的控制流
由于小方法会被内联,所以getter/setter等的调用开销,for循环里调用其他热更内方法的性能也会有所提升
如果一个方法既没有数值计算,又没有频繁调用热更内小方法或者访问property,主要由调用系统或UnityAPI组成,则不会产生任何优化,一些情况下可能性能还低于传统模式
寄存器模式是为了提高数值计算的性能,也是为了提高类似于getter/setter访问器这样的小方法的调用开销,如果这两种都没有的话就不需要内联,因为内联有时优化不好会导致它的性能低于传统模式
ILRunTime寄存器模式的使用建议

ILRunTime推荐的使用模式有二种:
AppDomain构造函数时不指定JIT模式,即默认使用传统模式执行,在遇到就要优化的密集计算型方法时,对该方法指定JITImmediately模式
直接在AppDomain构造函数处指定JITDemand模式
第一种用法对现有实现影响最小,仅在需要优化处开启,可以比较精准的控制执行效果。如果并不知道在什么时候应该使用何种模式,也可以直接使用JITDemand模式,让ILRunTime自行决定运行模式,在大多数情况下是能达到不错的性能平衡的
比如在A寻路算法里有大量的距离计算,这里就可以对A寻路里的算法类开启JIT模式
如果不想那么复杂,想让它智能一点,可以·在进行大量数学计算的时候自动开启,就可以使用JITDemand
如何开启JITDemand

确保项目切换到ILRunTime模式

编译生成代码
要进行ILRunTime性能优化有这么几点
1,生成绑定代码
2,开启JIT模式
要开启ILRunTime的JIT模式需要在生成的代码中的创建appdomain语句中加入IlRuntimeJITFlags.JITDemand

回到Unity中按F7生成代码,然后启动游戏
游戏启动时会在Console面板中输出信息

将性能分析器打开,这里需要注意的是CPU的开销、Drawcall的批次数、以及帧率

下方是我们的《皇室战争》游戏项目,因为游戏原本是竖屏游戏,所以UI有些变形
可以看到性能分析器中的帧率在游戏战斗进行时保持在30帧左右,在游戏结束时帧率会恢复到150帧,这是采用了ILRunTime性能优化的结果

建立一个jitMode变量,并将JIT模式设置None,也就是不开启JIT模式

回到Unity按一下F7生成代码并启动游戏

可以看到游戏项目的帧率在游戏战斗进行时始终保持在20帧左右,而且游戏结束时帧率也只是在80帧左右
可以看出没有开启JIT模式的游戏性能比刚才低了将近一倍,所以开启JIT模式是很有意义的
开发心得
以上就是对于JIT模式的大致介绍,另外我在开发上有一点小小的心得建议
由于寄存器模式是经过了再编译,再简易执行的过程,所以它有的时候是不太稳定的,要解决这个问题就需要确保你的ILRunTime代码在不开启寄存器模式时能够正常运行
确保了这点以后再去开启JIT模式,这样就可以单独的测试JIT模式是否正常,这比把所有的问题搅在一起,然后去找问题要简单的多
写在最后
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
详细内容可观看下方完整视频


