安卓系统下luajit性能问题

我今天新建了一个空项目,在MyApp:onCreate函数中加入如下测试代码:
local verbo = require(“jit.v”)
verbo.start()

print("[jit] version",jit.version)
print("[jit] version_num",jit.version_num)
print("[jit] os",jit.os)
print("[jit] arch",jit.arch)

print("[jit] status on ", jit.status())

local x = 0
for i=1,1000000 do
x = x + 1
end

print("[jit] x",x)

jit.off()
print("[jit] status off", jit.status())

x = 0
for i=1,1000000 do
x = x + 1
end

print("[jit] x",x)
结果发现运行时,仍然是有大量的异常打印,如下:
03-28 19:28:00.355 4299 4316 D cocos2d-x debug info: [LUA-print] 1[TRACE — “app/MyApp.lua”:21 – failed to allocate mcode memory at “app/MyApp.lua”:22]
03-28 19:28:00.355 4299 4316 D cocos2d-x debug info:
03-28 19:28:00.355 4299 4316 D cocos2d-x debug info: [LUA-print] 7[TRACE flush]

==============================
请问这个代表什么情况,是否说明并未在luajit正常模式下运行?

你引擎的版本是什么?在哪个平台运行?
jit.v是jit自带的吗?

引擎版本3.10, 在安卓平台运行

jit.v 是LuaJit自带的一个打印调试工具,使用前只需要将LuaJit源码中的src/jit目录拷贝到项目目录中即可。

不太了解这些信息的含义。luajit本身应该是没问题的。

luajit本身是没有问题的,但是在cocos项目中可能由于初始化内存无法分配导致有问题了,直接的证据就是jit.off()状态下比jit.on()状态下明显效率更高。

这里有篇文字对此进行了说明
http://www.cnblogs.com/zwywilliam/p/5999980.html

3.1可供代码执行的内存空间被耗尽->要么放弃jit,要么修改luajit的代码

要jit,就要编译出机器码,放到特定的内存空间。但是arm有一个限制,就是跳转指令只能跳转前后32MB的空间,这导致了一个巨大的问题:luajit生成的代码要保证在一个连续的64MB空间内,如果这个空间被其他东西占用了,luajit就会分配不出用于jit的内存,而目前luajit会疯狂重复尝试编译,最后导致性能处于瘫痪的状态。
虽然网上有一些不修改luajit的方案(http://www.freelists.org/post/luajit/Performance-degraded-significantly-when-enabling-JIT,9),在lua中调用luajit的jit.opt的api尝试将内存空间分配给luajit,但根据我们的测试,在unity上这样做仍然无法保证所有机器上能够不出问题,因为这些方案的原理要抢在这些内存空间被用于其他用途前全部先分配给luajit,但是ulua可以运行的时候已经是程序初始化非常后期的阶段,这个时候众多的unity初始化流程可能早已耗光了这块内存空间。相反cocos2dx这个问题并不多见,因为luajit运行早,有很大的机会提前抢占内存空间。
无论从代码看还是根据我们的测试以及luajit maillist的反馈来看,这个问题早在2.0.x就存在,更换2.1.0依然无法解决,我们建议,如果项目想要使用jit模式,需要在android工程的Activity入口中就加载luajit,做好内存分配,然后将这个luasate传递给unity使用。如果不愿意趟这个麻烦,那可以根据项目实际测试的情况,考虑禁用jit模式。一般来说,lua代码越少,遇到这个问题的可能性越低。

3.2寄存器分配失败->减少local变量、避免过深的调用层次

很不幸的一点是,arm中可用的寄存器比x86少。luajit为了速度,会尽可能用寄存器存储local变量,但是如果local变量太多,寄存器不够用,目前jit的做法是:放弃治疗(有兴趣可以看看源码中asm_head_side函数的注释)。因此,我们能做的,只有按照官方优化指引说的,避免过多的local变量,或者通过do end来限制local变量的生命周期。

3.3调用c函数的代码无法jit->使用ffi,或者使用2.1.0beta2

这里要提醒一点,调用c#,本质也是调用c,所以只要调用c#导出,都是一样的。而这些代码是无法jit化的,但是luajit有一个利器,叫ffi,使用了ffi导出的c函数在调用的时候是可以jit化的。
另外,2.1.0beta2开始正式引入了trace stitch,可以将调用c的lua代码独立起来,将其他可以jit的代码jit掉,不过根据作者的说法,这个优化效果依然有限。

3.4jit遇到不支持的字节码->少用for in pairs,少用字符串连接

有非常多bytecode或者内部库调用是无法jit化的,最典型就是for in pairs,以及字符串连接符(2.1.0开始支持jit)。
具体可以看http://wiki.luajit.org/NYI,只要不是标记yes或者2.1的代码,就不要过多使用。

4.怎么知道自己的代码有没有jit失败?使用v.lua

完整的luajit的exe版本都会带一个jit目录,下面有大量luajit的工具,其中有一个v.lua,这是luajit verbose mode(另外还有一个很重要的叫p.lua,luajit profiler,后面会提到),可以追踪luajit运行过程中的一些细节,其中就可以帮你追踪jit失败的情况。
local verbo = require(“jit.v”)
verbo.start()
当你看到以下错误的时候,说明你遇到了jit失败
failed to allocate mcode memory,对应错误3.1
NYI: register coalescing too complex,对应错误3.2
NYI: C function,对应错误3.3(这个错误在2.1.0beta2中已经移除,因为有trace stitch)
NYI: bytecode,对应错误3.4
这在luajit.exe下使用会很正常,但要在unity下用上需要修改v.lua的代码,把所有out:write输出导向到Debug.Log里头。

学习了,我建了issue.