効率的なLuaコードを記述する方法-1-基本知識


『Lua Programming Gems』Chapter 2:Lua Performance Tips:Basic fact By Roberto Ierusalimschy
効率的なLuaコードを記述する方法
基本的な知識
Luaはコードを実行する前に、まずソースコードを内部コードに翻訳(プリコンパイル)し、このコードは一連の仮想マシンが命令を識別できるように構成され、CPUのマシンコードとよく似ている.次に、Cコードのwhileサイクルがこれらの内部符号化を説明し、このwhileサイクルには大きなswitchがあり、1つの命令には対応するcaseがある.
他の場所から知っているかもしれませんが、5.0バージョンからLuaはレジスタベースの仮想マシンを使用しています.しかし、これらの「レジスタ」は、Luaが移植性を失い、Luaが利用可能なレジスタ数に制限されるため、CPU内のレジスタとは何の関連もありません.Luaは、スタック(配列にいくつかのインデックスを加えて実装される)を使用してレジスタを格納する.各実行中の関数には、スタックに格納され、各関数に対応するレジスタが内部に格納されたそれぞれのアクティビティレコードがあります.したがって、各関数にはそれぞれのレジスタのセットがあります.各命令のうち8 bitのみがレジスタをフラグするため、各関数は最大250個のレジスタを使用することができる.
Luaにはこのような大量のレジスタがあるため,プリコンパイル時にすべての局所変数(local)をレジスタに格納することができる.したがって,Luaでは局所変数へのアクセスが速い.例えば、aおよびbが局所変数である場合、文a=a+bは、ADD 0 0 1(aおよびbがそれぞれレジスタ0および1にあると仮定する)のみを生成する.比較すると、aとbがグローバル変数である場合、上記の加算演算を生成する中間コードは、次のようになります.
 
GETGLOBAL 0 0  ; a
GETGLOBAL 1 1  ; b
ADD            0 0 1
SETGLOBAL  00   ; a
 
だから、Luaプログラミングの中で最も重要な性能改善のルールの一つを得ることができます:局所変数(uselocals)を使用します!
プログラムのパフォーマンスを最大限に向上させる必要がある場合は、ローカル変数を使用します.たとえば、長いループで関数を呼び出す場合は、まず関数をローカル変数に割り当てることができます.例えば以下のコード
for i = 1, 1000000 do
    local x= math.sin(i)
end

次のコードよりも30%遅くなります.
local sin = math.sin
for i = 1, 1000000 do
    local x= sin(i)
end

外層ローカル変数(すなわち、外層関数のローカル変数)へのアクセスは、ローカル変数へのアクセスが速いわけではありませんが、グローバル変数へのアクセスよりも速いです.次のコードクリップを見てください.
function foo(x)
    for i =1, 1000000 do
        x =x + math.sin(i)
    end
    return x
end
print(foo(10))

foo関数の外でsinを宣言することで最適化します.
local sin = math.sin
function foo(x)
    for i =1, 1000000 do
        x =x + sin(i)
    end
    return x
end
print(foo(10))

第2セグメントコードは第1セグメントより30%速い.
他のコンパイラよりもLuaのコンパイラの方が効率的であるが,コンパイルは比較的煩雑なタスクである.したがって、loadstring関数を呼び出すなど、プログラムでコードをコンパイルすることは常に避けなければなりません.コードがユーザーによって入力されるなど、コードを本当に動的に実行する必要がない限り、動的なコードをコンパイルする必要はありません.
次の例を考慮すると、次のコードは、tableに格納された関数が定数1から10000を返す10000関数を格納したtableを作成します.
local lim = 10000
local a = {}
for i = 1, lim do
    a[i] =loadstring(string.format("return %d", i))
end
print(a[10]()) --> 10

このコードは1.4秒実行されました.
閉パッケージを使用することで、動的コンパイルを回避します.次のコードは、1/10の時間(0.14)で同じ10000個の関数を作成します.
function fk (k)
    returnfunction () return k end
end
local lim = 100000
local a = {}
for i = 1, lim do a[i] = fk(i) end
print(a[10]()) --> 10