Friday, March 15, 2013

探索Lua5.2內部實現:虛擬機指令(4) Table


nameargsdesc
OP_NEWTABLEABCR(A) := {} (size = B,C)
NEWTABLE在寄存器A處創建一個table對象。B和C分別用來存儲這個table數組部分和hash部分的初始大小。初始大小是在編譯期計算出來並生成到這個指令中的,目的是使接下來對table的初始化填充不會造成rehash而影響效率。B和C使用“floating point byte”的方法來表示成(eeeeexxx)的二進制形式,其實際值為(1xxx) * 2^(eeeee-1)。
  1. local a = {};  
  1. 1 [1] NEWTABLE 0 0 0  
  2. 2 [1] RETURN 0 1   
上面代碼生成一個空的table,放入local變量a,B和C參數都為0。

nameargsdesc
OP_SETLISTABCR(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
SETLIST用來配合NEWTABLE,初始化表的數組部分使用的。A為保存待設置表的寄存器,SETLIST要將A下面緊接著的寄存器列表(1--B)中的值逐個設置給表的數組部分。
當表需要初始化數組元素數量比較小的情況下,例如:
  1. local a = {1,1,1};  
  1.     1 [1] NEWTABLE 0 3 0  
  2.     2 [1] LOADK 1 -1 ; 1  
  3.     3 [1] LOADK 2 -1 ; 1  
  4.     4 [1] LOADK 3 -1 ; 1  
  5.     5 [1] SETLIST 0 3 1 ; 1  
  6.     6 [1] RETURN 0 1  
  7. constants (1) for 0x80048eb0:  
  8.     1 1   
第1行先用NEWTABLE構建一個具有3個數組元素的表,讓到寄存器0中;然後使用3個LOADK向下面3個寄存器裝入常量1;最後使用SETLIST設置表的1~3為寄存器1~寄存器3。
如果需要創建一個很大的表,其中包含很多的數組元素,使用如上方法就會遇到一個問題。將這些指按順序放到寄存器時,會超出寄存器的範圍。解決的辦法就是按照一個固定大小,將這些數組元素分批進行設置。在Lua中,每批的數量由lopcodes.h中的LFIELDS_PER_FLUSH定義,數量為50。所以,大數量的設置會按照50個一批,先將值設置到表下面的寄存器,然後設置給對應的表項。C代表的就是這一次調用SETLIST設置的是第幾批。回到上面的例子,因為只有3個表項,所以1批就搞定了,C的值為1。
下面是一個大表的設置:
  1. local a =   
  2. {  
  3.     1,2,3,4,5,6,7,8,9,0,  
  4.     1,2,3,4,5,6,7,8,9,0,  
  5.     1,2,3,4,5,6,7,8,9,0,  
  6.     1,2,3,4,5,6,7,8,9,0,  
  7.     1,2,3,4,5,6,7,8,9,0,  
  8.     1,2,3  
  9. };  
  1.     1 [1] NEWTABLE 0 30 0  
  2.     2 [3] LOADK 1 -1 ; 1  
  3.     3 [3] LOADK 2 -2 ; 2   
  4. ...  
  5.     50 [7] LOADK 49 -9 ; 9  
  6.     51 [7] LOADK 50 -10 ; 0  
  7.     52 [7] SETLIST 0 50 1 ; 1  
  8.     53 [8] LOADK 1 -1 ; 1  
  9.     54 [8] LOADK 2 -2 ; 2  
  10.     55 [9] LOADK 3 -3 ; 3  
  11.     56 [9] SETLIST 0 3 2 ; 2  
  12.     57 [9] RETURN 0 1  
  13. constants (10) for 0x80048eb0:  
  14.     1 1  
  15.     2 2  
  16.     3 3  
  17.     4 4  
  18.     5 5  
  19.     6 6  
  20.     7 7  
  21.     8 8  
  22.     9 9  
  23.     10 0   
可以看到,這個表的初始化使用了兩個SETLIST指令。第一個處理前50個,C為1,設置id從(C-1)*50 + 1開始,也就是1。第二個處理餘下的3個,C為2,設置的id從(C-1)*50 + 1開始,也就是51。
如果數據非常大,導致需要的批次超出了C的表示範圍,那麼C會被設置成0,然後在SETLIST指令後面生成一個EXTRAARG指令,並用其Ax來存儲批次。這與前面說到的LOADKX的處理方法一樣,都是為處理超大數據服務的。
如果使用核能產生多個返回值的表達式(... 和函數調用)初始化數組項,如果這個初始化不是表構造的最後一項,那麼只有第一個返回值會被設置到數組項;如果是最後一項,那麼SETLIST中的B會被設置為0,表示從A+1到當前棧頂都用來設置。
SETLIST只負責初始化表的數組部分,對於hash部分,還是通過SETTABLE來初始化。
nameargsdesc
OP_GETTABLEABCR(A) := R(B)[RK(C)]
OP_SETTABLEABCR(A)[RK(B)] := RK(C)
GETTABLE使用C表示的key,將寄存器B中的表項值獲取到寄存器A中。SETTABLE設置寄存器A的表的B項為C代表的值。
  1. local a = {};  
  2. ax = 1;  
  3. local b = ax;  
  1. 1 [1] NEWTABLE 0 0 0  
  2. 2 [2] SETTABLE 0 -1 -2 ; "x" 1  
  3. 3 [3] GETTABLE 1 0 -1 ; "x"  
  4. 4 [3] RETURN 0 1   

No comments: