首页 > Personal > Unity IOS 闪退之trampolines
2014
09-12

Unity IOS 闪退之trampolines

unity游戏工程生成IOS版本后,在真机运行是经常莫名的闪退,看xcode日志报错Run out of trampolines of type 1/2。google一下找到大致原因,Mono Runtime的官方文档描述,

trampolines are small, hand-written pieces of assembly code used to perform various tasks in the mono runtime. They are generated at runtime using the native code generation macros used by the JIT. They usually have a corresponding C function they can fall back to if they need to perform a more complicated task. They can be viewed as ways to pass control from JITted code back to the runtime.

Trampoline是一些手写的非常短小的用来在mono运行时中执行很多操作的组件代码。主要是通过JIT使用到的本地代码宏在运行时动态生成的。它们通常都有与之相对应的C方法,在某些较为复杂的场景中,当trampoline无法胜任时,mono运行时就会将这些复杂的操作交回给这些对应的C方法来执行。这也可以看作是将JIT代码的执行权交回给runtime的一种方式。

JIT Trampolines These trampolines are used to JIT compile a method the first time it is called. When the JIT compiles a call instruction, it doesn’t compile the called method right away. Instead, it creates a JIT trampoline, and emits a call instruction referencing the trampoline. When the trampoline is called, it calls mono_magic_trampoline () which compiles the target method, and returns the address of the compiled code to the trampoline which branches to it. This process is somewhat slow, so mono_magic_trampoline () tries to patch the calling JITted code so it calls the compiled code instead of the trampoline from now on. This is done by mono_arch_patch_callsite () in tramp-.c.

JIT Trampolines 这些Trampoline主要是JIT在首次调用某个方法的时候编译方法用的。当JIT在编译一个方法调用指令时,它并不会立刻就编译这个被调用到的方法。实际上,它会先创建一个JIT Trampoline,同时创建一个指向这个trampoline的调用指令。当这个JIT Trampoline在调用到的时候,它会再调用mono_magic_trampoline()方法来编译这个trampoline实际指向的目标方法,然后将编译后的方法的指针地址返回给这个指向它的trampoline。这个过程呢稍微有点慢,所以呢,mono_magic_trampoline()方法会优化调用JIT代码的过程,它会先尝试调用已经通过JIT编译过的方法而不是立即通过trampoline直接进行调用。这些都是通过在tramp-.c文件中的mono_patch_callsiete()方法来完成的。

AOT Trampolines
These are similar to the JIT trampolines but instead of receiving a MonoMethod to compile, they receive an image+token pair. If the method identified by this pair is also AOT compiled, the address of its compiled code can be obtained without loading the metadata for the method.

AOT Trampolines和JIT Trampolines非常相似,但是AOT Trampolines接受的编译参数不是一个Mono方法而是一个image+token对。如果传入的用于编译的image+token对所指向的方法已经经过AOT编译过了,那么再次编译这个image+token对时,就会直接返回这个已编译方法的指针地址而不需要再次加载这个方法的元数据进行再次编译了。

On device we generate all the necessary code at build time in a process known as Ahead of Time compilation (similar to Microsoft’s ngen), because we’re not allowed to jit code on devices. Unfortunately there are a few things that cannot be determined statically – for instance generic interfaces might need different vtables depending on which type the interface is instantiated with. (For this case it is technically possible to determine the maximum number of vtables, but the number would be potentially enormous – multiply the number of generic interfaces times the number of types in your app…). We cannot allocate memory for these vtables dynamically at runtime, so we’ve picked a reasonable default and allow the user to increase this value if they run into issues. This is the basic theory for the trampolines (the exact problem is a bit different, depending on the type of trampolines, but that’s not really important).

So you can add as many trampolines as you want, but memory usage will increase. That’s also all there is to it: the app will not get slower (unless if the increased memory usage causes it to run slower, due to out-of-memory warnings, etc). It also means that you only have to increase the number of trampolines of the type you’re actually having problems with, if you increase the others you’ll increase the size of your executable needlessly.

因为mono没有办法在ios设备上使用JIT代码,所以在编译的时候mono会通过AOT技术直接编译为ARM汇编代码。但在编译的时候仍有一些无法静态确定的事情:如泛型接口需要不同的虚表(运行时存放执行方法的集合),这取决于接口被实例化时的类型。(从技术上讲确定虚表的最大数量是可能的,但是这数量可能很大,即泛型接口数乘以应用中类型数),我们无法为这些虚表在运行时动态的分配内存,所以指定了一个合理的默认值,并允许用户在运行时出现问题的情况下增加默认值。这就是trampoline的基本理论(实际情况略微不同,这依赖于trampoline的类型)。例如使用大量的递归泛型,通常会发生这种错误。

所以你可以按需要尽可能的增加trampoline,但是使用内存也会增加。 应用一般不会变慢(除非内存不足)。你仅需要增加发生问题的那块对应的trampoline类型的数目。如果你增加了其他trampoline类型的数目,只是增加了不必要的执行内存。

Trampolines存在的价值就是为了减少C#代码在mono runtime中运行时的性能损耗,提高C#代码的执行效率。
Unity3D在iOS和Android设备上的发布都选择了使用AOT编译机制来实现。
AOT是区别于JIT(Just In Time)的另一个编译机制,全称是Ahead Of Time,就是预先编译好,而不是在代码执行到了某个方法再进行编译。
使用AOT编译的有点有以下优点: 1. 加快程序启动速度 2. 更强的内存共享机制 3. 潜在的性能提升
也会有一些限制,例如支持平台的有限,支持AOT的Mono版本有限等等,具体信息可以参考Mono官方AOT介绍文档。

解决方法就是让AOT编译器来分配更多type1和type2类型的trampolines,type1是指nrgctx-trampolines(递归泛型使用的空间,默认是1024),type2是指nimt-trampolines(接口使用的空间,默认是128),还有一个ntrampolines(泛型方法调用使用的空间,默认是1024)。AOT编译器命令行选项可以在Player Settings的Other Setings部分配置,配置给如下
nrgctx-trampolines=8096,nimt-trampolines=8096,ntrampolines=4048

最后编辑:
作者:wy182000
这个作者貌似有点懒,什么都没有留下。

留下一个回复