Хе-хе, я ждал этого вопроса. Как говорится, пути компиляторов неисповедимы: к примеру, если каким-либо образом обрабатывать элементы таблицы, создавая иллюзию реальной деятельности в теле цикла, то ipairs, разумеется, проигрывает, т.к. это функция-итератор, которая чисто технически не может быть быстрее простого цикла с числовым лимитом:
Bench()
:Add("for i = 1, #tbl do", function()
for i = 1, #tbl do
local var = math.floor(#tbl[i])
end
end)
:Add("for i, v in ipairs(tbl) do", function()
for i, v in ipairs(tbl) do
local var = math.floor(#v)
end
end)
:Start(100000000)
Если же получить элемент таблицы и ничего с ним не делать, то компилятор оптимизирует тело второго цикла, банально удалив "лишнее" присвоение. При этом индексация в первом случае останется нетронутой, и создастся иллюзия ускорения:
Bench()
:Add("for i = 1, #tbl do", function()
for i = 1, #tbl do
local var = tbl[i]
end
end)
:Add("for i, v in ipairs(tbl) do", function()
for i, v in ipairs(tbl) do
local var = v
end
end)
:Start(100000000)
А вот в этом случае ipairs опять в пролёте даже несмотря на дополнительную индексацию по таблице в теле первого цикла, т.к. накладные расходы на вызов функции-итератора превышают расходы на индексацию таблиц:
Bench()
:Add("for i = 1, #tbl do", function()
for i = 1, #tbl do
local var = #tbl[i] + math.floor(i)
end
end)
:Add("for i, v in ipairs(tbl) do", function()
for i, v in ipairs(tbl) do
local var = #v + math.floor(i)
end
end)
:Start(100000000)