luaの内部最適化について

これメモリ確保してる?無駄に新しいの生成してない?など、luaの内部挙動についてマニュアルを見ても不明だった点を2,3書き残しておきたいと思います。
内部処理は今風でとても賢いです:Q

文字列の受け渡し

luaの引数の説明では「参照渡しされるのはテーブル」と表記されていて文字列に関しては明記されていません。てっことはもしかしてコピーされちゃうの?
というわけで、allocをフックしてメモリ確保の流れをログします。

local str = "test case !!!!!!!!!!!!!!!!"
custom.alloclog(true)
testcall(str)
print(str)
custom.alloclog(false)

function testcall(str)
  print(str)
end

custom.alloclog()はメモリ確保、解放ログ出力の自前命令、仮命令です。関数の呼び出し時にコピーされるか確認します。
一応参照渡しチェックと言うことで、元strの内容も出力しておきましょうか。

test case !!!!!!!!!!!!!!!!
test case !!!!!!!!!!!!!!!!

メモリ確保ログが出ないのでコピーされていません。”低レベル層”では参照渡しがされているのがわかります。
というわけで内部で連結してみましょう。

function testcall(str)
  str = str.."hoge!"
  print(str)
end
ptr:20890C0 osize:133 nsize:138 usage:244K
ptr:23AB9B0 osize:4 nsize:155 usage:244K
test case !!!!!!!!!!!!!!!!hoge!
test case !!!!!!!!!!!!!!!!

内部連結分の確保が現れましたが大元strの内容はちゃんと維持されますので、”高レベル層”では値渡しでコピーされているかのような振る舞いです。
ちゃんと今風な言語の文字列の動き(値渡しのような振る舞いが出来る文字列)で賢い文字列でした。ありがたい!
文字列においては変更が無い限り生成が発生しないので、数字などと同じコスト(リスク無し)で渡せます。
なので、

function hogeclass:getdata()
  return self.data
end
function hogeclass:setdata(d)
  self.data = d
end

というような、dataが100MBをこえる大きなバイナリデータのやりとりにgetter/setterを挟んでもコストが発生することはありません。安心安全。今すぐインストールです。


おまけ。
luaコンパイルにはちゃんと固定値(const)判断っぽいようなのもあるようで、

custom.alloclog(true)
local str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
custom.alloclog(false)

このコードが実行されるときに"aaaaaaa...."のメモリが確保されることはありません。低レベル層では固定値"aaaaaaaaaaa...."の参照渡しで処理されます。
なので、関数の外に固定値として「const_name="hogehoge"」として宣言しなくても最適化してくれるようです。
ただし、テーブルはその限りではありませんのでご注意ください。

functionのキャッシュ

次はfunctionのコンパイル挙動について。
自分がよく使って怒られる、関数内関数みたいなものの場合のコストについてです。

function hoge(x1)
  function inhoge(x1,x2)
    return x1*x2
  end

  return inhoge(x1+1,x1+2)
end

もうちょっと低レベルに展開すると

function hoge(x1)
  local inhoge = function(x1,x2)
    return x1*x2
  end

  return inhoge(x1+1,x1+2)
end

となり、inhogeに無名関数が突っ込まれる形になります。こうしてみると、hoge()が呼ばれるごとにinhoge()のコンパイルが発生しそうですよね。
それを調べるためにinhoge()の関数ポインタを出力させます。

function hoge(x1)
  local inhoge = function(x1,x2)
    return x1*x2
  end
  print( tofunction(inhoge) )
  return inhoge(x1+1,x1+2)
end
function: 023D3998
function: 023D3998
function: 023D3998
function: 023D3998

全部同値が返ってきたので、function(関数)にはちゃんと使い回せるようにキャッシュみたいなのがあるようです。賢い。
えー同じになってるだけじゃ無いの〜と言うわけで100000ループテストで、関数を外に出した場合(読込時にコンパイルされる位置)と比較してもほぼ同値でしたので問題ないでしょう…。
これで安心して関数内関数をご使用ください。