Lua5.4で#(長さ演算子)が早くなったらしいのでベンチマークしてみた


はじめに

Luaではtable.insert()という関数が標準ライブラリとして用意されています。
しかし、この関数は遅く、単に末尾に要素を追加するだけならもっと早い方法があります。
その方法としては大きく2つ、カウンターを使う方法と#(長さ演算子)を使う方法が知られていると思います。
このうち、長さ演算子が5.4で高速化したと聞いたので、簡単にベンチを取ってみました。

使用したソースコード

1から1,000,000までの連番で配列を作る速度を競います。
グローバル変数へのアクセス自体が遅いという話もあるので、一度table.insertをローカル変数に束縛する方法も試してみます。

local function mean(arr)
  local sum = 0
  for i = 1, #arr do
    sum = sum + arr[i]
  end
  return sum / #arr
end

local time_insert = {}
for i = 1, 10 do
  time_insert[i] = os.clock()
  local new = {}
  for j = 1, 1000000 do
    table.insert(new, j)
  end
  time_insert[i] = os.clock() - time_insert[i]
end
print("table.insert: " .. mean(time_insert))

local time_insert2 = {}
for i = 1, 10 do
  time_insert2[i] = os.clock()
  local insert = table.insert
  local new = {}
  for j = 1, 1000000 do
    insert(new, j)
  end
  time_insert2[i] = os.clock() - time_insert2[i]
end
print("local insert: " .. mean(time_insert2))

local time_counter = {}
for i = 1, 10 do
  time_counter[i] = os.clock()
  local new = {}
  local c = 0
  for j = 1, 1000000 do
    c = c + 1
    new[c] = j
  end
  time_counter[i] = os.clock() - time_counter[i]
end
print("counter: " .. mean(time_counter))

local time_length = {}
for i = 1, 10 do
  time_length[i] = os.clock()
  local new = {}
  for j = 1, 1000000 do
    new[#new + 1] = j
  end
  time_length[i] = os.clock() - time_length[i]
end
print("length: " .. mean(time_length))

結果

比較用のLua5.3.3だとこうなります。

table.insert: 0.1646712
local insert: 0.1468932
counter: 0.0185333
length: 0.1276985

lua5.4.3だとこう。

table.insert: 0.0581953
local insert: 0.0498282
counter: 0.0133932
length: 0.0204012

全体的に早くなっていますが、特に#はかなり早くなっていますね。6倍くらいです。
table.insert()も倍以上早くなっているのは驚きでした。

結論

しかし一番早いのはカウンター使う方法で変わりませんでした。うーん。

おまけ

neovimにbuilt-inされているluajitだとこうなります

table.insert: 0.0054451
local insert: 0.0057246
counter: 0.0052584
length: 0.0056855