LuaJIT 是如何工做的(上)

上一篇「Lua 代碼是如何跑起來的」 中,咱們介紹了標準 Lua 虛擬機是如何運行 Lua 代碼的。c++

今天咱們介紹 Lua 語言的另一個虛擬機實現 LuaJIT,LuaJIT 使用的 lua 5.1 的語言標準(也能夠兼容 lua 5.2)。意味着一樣一份遵照 lua 5.1 標準的代碼,既能夠用標準 lua 虛擬機來跑,也能夠用 LuaJIT 來跑。segmentfault

LuaJIT 主打高性能,接下來咱們看看 LuaJIT 是如何提升性能的。函數

解釋模式

首先,LuaJIT 有兩種運行模式,一種是解釋模式,這個跟標準 Lua 虛擬機是相似的,不過也有改進的地方。性能

首先,跟標準 Lua 虛擬機同樣,Lua 源代碼是被編譯爲字節碼(byte code),而後一個個的解釋執行這些字節碼。
可是,編譯出來的字節碼,並非跟標準 Lua 同樣,只是相似。
模式上來講,LuaJIT 也是基於虛擬寄存器的,雖然具體實現方式上有所區別。優化

解釋執行字節碼

從 Lua 源碼到字節碼,其實差別不大,可是解釋執行字節碼,LuaJIT 的改進動做就比較大了。lua

Lua 解釋執行字節碼,是在 luaV_execute 這個 C 函數裏實現的,而 LuaJIT 則是經過手寫彙編來實現的。
一般,咱們會簡單的認爲手寫彙編就會更高效,不過也得看寫代碼的質量。設計

對比最終生成的機器碼

此次咱們經過實際對比雙方最終生成的機器碼,體驗下手寫的彙編是如何作到高效的。code

咱們對比「字節碼解析」這部分的實現。
首先,Lua 和 LuaJIT 的字節碼,都是 32 位定長的。字節碼解析的基本邏輯便是:
從虛擬機內部維護的 PC 寄存器,讀取 32 位長的字節碼,而後解析出 OP 操做碼,以及對應的操做參數。get

LuaJIT

下面 LuaJIT 源碼中的「字節碼解析」的源代碼,
這裏並非裸寫的彙編代碼,爲了提升可閱讀性,用到了一些宏。源碼

mov RCd, [PC]
movzx RAd, RCH
movzx OP, RCL
add PC, 4
shr RCd, 16

最終在 x86_64 上生成的機器指令以下,很是的簡潔。

mov    eax,DWORD PTR [rbx]  # rbx 裏存儲的是 PC 值,讀取 32 位字節碼到 eax 寄存器
movzx  ecx,ah               # 9-16 位,是操做數 A 的值
movzx  ebp,al               # 低 8 位是 OP 操做碼
add    rbx,0x4              # PC 指向下一個字節碼
shr    eax,0x10             # 右移 16 位,是操做數 C 的值
Lua

在 Lua 的 luaV_execute 函數中,大體是有這些 C 源代碼來完成「字節碼解析」的部分工做。

const Instruction i = *pc++;
ra = RA(i);
GET_OPCODE(i)

通過 gcc 編譯以後,咱們從可執行文件中,能夠找到以下相對應的機器指令。
由於 gcc 是對整個函數進行通盤優化,因此指令的順序並非那麼直觀,寄存器使用也不是那麼統一,因此看起來會有點亂。
以下是我摘出來的機器指令,爲了方便閱讀,順序也通過了調整,沒有保持原始的順序。

mov    ebx,DWORD PTR [r14]   # r14 裏存儲的是 PC 值,讀取 32 位字節碼到 ebx 寄存器
lea    r12,[r14+0x4]         # PC 指向下一個字節碼,存入 r12
mov    r14,r12               # 後續再複製到 r14(由於 r14 中間還有其餘用途)

mov    edx,ebx               # 複製 edx 到 eax
and    edx,0x3f              # 低 6 位是 OP 操做碼

# 7-14 位是操做數 A 的值
mov    eax,ebx               # 複製 ebx 到 eax
shr    eax,0x6               # 右移 6 位
movzx  eax,al                # 此時的低 8 位是操做數 A 的值

# 此時對應操做數的使用,不屬於字節碼解析了,可是是 RA(i) 裏的實現
shl    rax,0x4               # rax * 16
lea    r9,[r11+rax*1]        # r11 是 BASE 的值,取操做 A 對應 Lua
 棧上的地址

對比分析

字節碼解析,是 Lua 中最基礎的操做。
經過對比最終生成的機器碼,咱們明顯能夠看到 LuaJIT 的實現能夠更加高效。

手寫彙編能夠更好的利用寄存器,不過,也不徹底是由於手寫彙編的緣由。
LuaJIT 從字節碼設計上,就考慮到了高效,OP code 直接是 8 位,這樣能夠直接利用 al 這種 CPU 硬件提供的低 8 位能力,能夠省掉一些位操做指令。

JIT

Just-In-Time 是 LuaJIT 運行 Lua 代碼的另外一種模式,也是 LuaJIT 的性能殺手鐗。
主要原理是動態生成更加功效的機器指令,從而提高運行時性能。

這個咱們下一篇再繼續...

相關文章
相關標籤/搜索