Я люблю Lua. I love Lua.

Скорость цикла for

Posted in Uncategorized by ilovelua on 8 августа, 2013

В чужом коде увидел такое:

for i = 1, #data.values do
	local value = data.values[i]
	...
end

И тут же осудил внутри себя этого нерадивого разработчика «Экий ты, брат, ленивец! Нет бы написать «правильно»!»:

for i, value in ipairs(data.values) do
	...
end

И тут меня сомнение взяло: второй вариант мне лично кажется красивее и правильнее, но быстр ли он? И быстро был написан тест:

local data = {
	values = {}
}

for i = 1, 10000 do
	table.insert(data.values, i)
end

local function simpleForTest()
	local j
	local t = os.clock()

	for k = 1, 100 do
		for i = 1, #data.values do
			j = data.values[i]
		end
	end

	print('~~~#', os.clock() - t)
end

local function ipairsTest()
	local j
	local t = os.clock()

	for k = 1, 100 do
		for i, val in ipairs(data.values) do
			j = val
		end
	end

	print('~~~ipairs', os.clock() - t)
end

for i = 1, 10 do
	simpleForTest()
	ipairsTest()
end

А результаты он давал такие:
~~~# 0.063
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122

То есть «некрасивый» код работает в два раза быстрее «красивого». Вот так.

Upvalues и вопросы производительности

Posted in Uncategorized by ilovelua on 23 июля, 2013

Дано: список событий(массив таблиц).
Задача: с делать фильтр по полям событий.

Решение 1(прямое, как палка):

function getEventVisible(event, filter)
	if filter.initiatorId then
		if filter.initiatorId ~= event.initiatorMissionID then
			return false
		end
	end

	if filter.targetId then
		if filter.targetId ~= event.targetMissionID then
			return false
		end
	end

	if filter.objectId then
		if event.initiatorMissionID ~= filter.objectId and event.targetMissionID ~= filter.objectId then
			return false
		end
	end

	if filter.weaponId then
		if filter.weaponId ~= event.weapon then
			return false
		end
	end

	if filter.eventType then
		if filter.eventType ~= eventEventType then
			return false
		end 
	end

	if filter.initiatorCoalition then
		if filter.initiatorCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.initiatorMissionID) then
			return false
		end
	end

	if filter.targetCoalition then
		if filter.targetCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.targetMissionID) then
			return false
		end
	end

	return true
end

Тут мы видим, что некоторые поля используются несколько раз. Это срочно нужно оптимизировать:

function getEventVisible2(event, filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName
	local eventInitiatorId = event.initiatorMissionID
	local eventTargetId = event.targetMissionID
	local eventWeaponId = event.weapon
	local eventEventType = event.type
	local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
	local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

	if initiatorId then
		if initiatorId ~= eventInitiatorId then
			return false
		end
	end

	if targetId then
		if targetId ~= eventTargetId then
			return false
		end
	end

	if objectId then
		if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
			return false
		end
	end

	if weaponId then
		if weaponId ~= eventWeaponId then
			return false
		end
	end

	if eventType then
		if eventType ~= eventEventType then
			return false
		end 
	end

	if initiatorCoalition then
		if initiatorCoalition ~= eventInitiatorCoalition then
			return false
		end
	end

	if targetCoalition then
		if targetCoalition ~= eventTargetCoalition then
			return false
		end
	end

	return true
end

Тут можно было бы откинуться на кресле и сделать глоток крепкого виски… Но чу! Это что же такое получается? Для фильтра, который один для всех событий, значения полей каждый раз извлекаются из таблицы filter. Непорядок. А ну-ка, где тут у нас upvalues?

function getFilterFunc(filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName

	return function(event)
		local eventInitiatorId = event.initiatorMissionID
		local eventTargetId = event.targetMissionID
		local eventWeaponId = event.weapon
		local eventEventType = event.type
		local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
		local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

		if initiatorId then
			if initiatorId ~= eventInitiatorId then
				return false
			end
		end

		if targetId then
			if targetId ~= eventTargetId then
				return false
			end
		end

		if objectId then
			if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
				return false
			end
		end

		if weaponId then
			if weaponId ~= eventWeaponId then
				return false
			end
		end

		if eventType then
			if eventType ~= eventEventType then
				return false
			end 
		end

		if initiatorCoalition then
			if initiatorCoalition ~= eventInitiatorCoalition then
				return false
			end
		end

		if targetCoalition then
			if targetCoalition ~= eventTargetCoalition then
				return false
			end
		end

		return true
	end
end

Ну а теперь…(ТЕСТЫ!!! ТЕСТЫ!!!)
Я не вижу ваши руки!

local events = DebriefingEventsData.getEvents()
local filterFunc = getFilterFunc(filter_)

print('~~~event count:', #events)

local t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible2(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible2', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if filterFunc(event) then
			table.insert(result, event)
		end
	end
end

print('~~~filterFunc', os.clock() - t)

Результаты:
~~~event count: 1467
~~~getEventVisible 1.32
~~~getEventVisible2 2.583
~~~filterFunc 2.286
~~~event count: 1467
~~~getEventVisible 0.698
~~~getEventVisible2 2
~~~filterFunc 1.706
~~~event count: 1467
~~~getEventVisible 1.326
~~~getEventVisible2 2.581
~~~filterFunc 2.284
~~~event count: 1467
~~~getEventVisible 1.642
~~~getEventVisible2 2.203
~~~filterFunc 1.984
~~~event count: 1467
~~~getEventVisible 1.506
~~~getEventVisible2 2.061
~~~filterFunc 1.822
~~~event count: 1467
~~~getEventVisible 1.338
~~~getEventVisible2 2.577
~~~filterFunc 2.275
~~~event count: 1467
~~~getEventVisible 0.75999999999999
~~~getEventVisible2 2.013
~~~filterFunc 1.712
~~~event count: 1467
~~~getEventVisible 1.322
~~~getEventVisible2 2.576
~~~filterFunc 2.281
~~~event count: 1467
~~~getEventVisible 1.323
~~~getEventVisible2 2.574
~~~filterFunc 2.288
~~~event count: 1467
~~~getEventVisible 1.687
~~~getEventVisible2 2.234
~~~filterFunc 1.999
~~~event count: 1467
~~~getEventVisible 1.717
~~~getEventVisible2 2.087
~~~filterFunc 1.808
~~~event count: 1467
~~~getEventVisible 1.743
~~~getEventVisible2 2.109
~~~filterFunc 1.83

Ой, что это? Наш прекрасный оптимизированный код работает хуже неоптимизированного? Похоже, что так. Причем местами «неоптимизированная» версия быстрее «оптимизированной» почти в 2 раза!
Думаю, что в getEventVisible2() создается слишком много локальных переменных, причем некоторые из них могут не понадобиться. А что не так с filterFunc()? Там вроде локальных переменных совсем немного? Похоже, что доступ к «замороженным» переменным не так уж и быстр, либо Lua при вызове этой функции должна откуда-то загрузить все upvalues.
В общем, как обычно с оптимизацией — все нужно проверять.

Вопросы производительности — 3

Posted in Uncategorized by ilovelua on 17 сентября, 2012

Рассмотрим скорость доступа модуля к локальной переменной и к полю, объявленному внутри модуля. Код модуля:

module('TestModule', package.seeall)

local localTable = {}
testTable = {}

-- добавляем в модуль полей, чтобы поиск осуществлялся не в совсем пустой таблице
for i = 1, 100 do
    _M['testTable' .. i] = i
end

function getTestTable()
    return testTable
end

function getLocalTable()
    return localTable
end

Код теста:

local TestModule = require('TestModule')

local count = 1000000

function test()
    local t = os.clock ()

    for i = 1, count do
        TestModule.getTestTable()[i] = i
    end

    local t1 = os.clock () - t

    t = os.clock ()

    for i = 1, count do
        TestModule.getLocalTable()[i] = i
    end

    local t2 = os.clock () - t

    return t1, t2
end

local percents = {}

for i = 1, 100 do
    local t1, t2 = test()
    local delta = string.format('%.6f', t1 - t2)
    local p = string.format('%.6f', ((t1 - t2) / t1) * 100)

    print(i, 'delta:', delta, '%', p)

    table.insert(percents, p)
end

local totalPercents = 0

for i, percent in ipairs(percents) do
    totalPercents = totalPercents + percent
end

print('average percents is:', totalPercents / #percents)

Результаты:

1 delta: 0.028000 % 9.395973
2 delta: 0.015000 % 5.514706
3 delta: 0.013000 % 4.779412
4 delta: 0.015000 % 5.494505
5 delta: 0.011000 % 4.044118
6 delta: 0.015000 % 5.494505
7 delta: 0.014000 % 5.128205
8 delta: 0.015000 % 5.474453
9 delta: 0.014000 % 5.147059
10 delta: 0.014000 % 5.147059
11 delta: 0.011000 % 4.044118
12 delta: 0.014000 % 5.147059
13 delta: 0.012000 % 4.411765
14 delta: 0.011000 % 4.074074
15 delta: 0.012000 % 4.411765
16 delta: 0.015000 % 5.494505
17 delta: 0.014000 % 5.128205
18 delta: 0.013000 % 4.779412
19 delta: 0.013000 % 4.761905
20 delta: 0.013000 % 4.797048
21 delta: 0.013000 % 4.779412
22 delta: 0.015000 % 5.494505
23 delta: 0.015000 % 5.494505
24 delta: 0.013000 % 4.779412
25 delta: 0.011000 % 4.059041
26 delta: 0.013000 % 4.779412
27 delta: 0.013000 % 4.761905
28 delta: 0.013000 % 4.779412
29 delta: 0.013000 % 4.797048
30 delta: 0.014000 % 5.147059
31 delta: 0.015000 % 5.494505
32 delta: 0.014000 % 5.147059
33 delta: 0.014000 % 5.128205
34 delta: 0.014000 % 5.147059
35 delta: 0.014000 % 5.147059
36 delta: 0.014000 % 5.147059
37 delta: 0.015000 % 5.494505
38 delta: 0.015000 % 5.494505
39 delta: 0.014000 % 5.147059
40 delta: 0.014000 % 5.147059
41 delta: 0.014000 % 5.128205
42 delta: 0.016000 % 5.860806
43 delta: 0.014000 % 5.147059
44 delta: 0.014000 % 5.147059
45 delta: 0.014000 % 5.147059
46 delta: 0.012000 % 4.411765
47 delta: 0.013000 % 4.779412
48 delta: 0.015000 % 5.514706
49 delta: 0.013000 % 4.779412
50 delta: 0.013000 % 4.779412
51 delta: 0.014000 % 5.128205
52 delta: 0.012000 % 4.411765
53 delta: 0.015000 % 5.494505
54 delta: 0.016000 % 5.860806
55 delta: 0.015000 % 5.494505
56 delta: 0.014000 % 5.147059
57 delta: 0.015000 % 5.514706
58 delta: 0.014000 % 5.147059
59 delta: 0.014000 % 5.147059
60 delta: 0.013000 % 4.779412
61 delta: 0.014000 % 5.147059
62 delta: 0.013000 % 4.779412
63 delta: 0.013000 % 4.761905
64 delta: 0.014000 % 5.147059
65 delta: 0.013000 % 4.779412
66 delta: 0.012000 % 4.411765
67 delta: 0.013000 % 4.797048
68 delta: 0.013000 % 4.779412
69 delta: 0.015000 % 5.494505
70 delta: 0.011000 % 4.044118
71 delta: 0.014000 % 5.147059
72 delta: 0.015000 % 5.494505
73 delta: 0.013000 % 4.779412
74 delta: 0.015000 % 5.494505
75 delta: 0.013000 % 4.779412
76 delta: 0.016000 % 5.860806
77 delta: 0.015000 % 5.514706
78 delta: 0.014000 % 5.147059
79 delta: 0.013000 % 4.779412
80 delta: 0.015000 % 5.494505
81 delta: 0.016000 % 5.818182
82 delta: 0.013000 % 4.761905
83 delta: 0.013000 % 4.779412
84 delta: 0.016000 % 5.860806
85 delta: 0.012000 % 4.411765
86 delta: 0.013000 % 4.779412
87 delta: 0.014000 % 5.128205
88 delta: 0.015000 % 5.514706
89 delta: 0.012000 % 4.411765
90 delta: 0.014000 % 5.147059
91 delta: 0.015000 % 5.494505
92 delta: 0.014000 % 5.147059
93 delta: 0.016000 % 5.860806
94 delta: 0.017000 % 6.204380
95 delta: 0.014000 % 5.147059
96 delta: 0.014000 % 5.147059
97 delta: 0.013000 % 4.779412
98 delta: 0.012000 % 4.411765
99 delta: 0.012000 % 4.411765
100 delta: 0.015000 % 5.494505
average percents is: 5.1020022

Здесь результаты не столь впечатляющие, как в предыдущем тесте, но тем не менее разница в ~5% есть.
Вывод: локальные переменные — наши друзья.

Вопросы производительности — 2

Posted in Uncategorized by ilovelua on 17 сентября, 2012

К вопросу о том, стоит ли заводить отдельные поля в модуле или пользоваться локальными переменными внутри модуля.

Код модуля:

module('TestModule', package.seeall)

local localTable = {}
testTable = {}

local count = 1000000

-- добавляем в модуль полей, чтобы поиск осуществлялся не в совсем пустой таблице
for i = 1, 100 do
    _M['testTable' .. i] = i
end

function fillTestTable()
    for i = 1, count do 
        testTable[i] = i
    end
end

function fillLocalTable()
    for i= 1, count do 
        localTable[i] = i
    end
end

Код теста:

local TestModule = require('TestModule')

function test()
    local t = os.clock ()
    TestModule.fillTestTable()
    local t1 = os.clock () - t

    t = os.clock ()
    TestModule.fillLocalTable()
    local t2 = os.clock () - t

    return t1, t2
end

local percents = {}

for i = 1, 100 do
    local t1, t2 = test()
    local delta = string.format('%.6f', t1 - t2)
    local p = string.format('%.6f', ((t1 - t2) / t1) * 100)

    print(i, 'delta:', delta, '%', p)

    table.insert(percents, p)
end

local totalPercents = 0

for i, percent in ipairs(percents) do
    totalPercents = totalPercents + percent
end

print('average percents is:', totalPercents / #percents)

Результаты:

1 delta: 0.020000 % 15.748031
2 delta: 0.023000 % 22.549020
3 delta: 0.022000 % 21.782178
4 delta: 0.022000 % 21.782178
5 delta: 0.022000 % 21.782178
6 delta: 0.022000 % 21.782178
7 delta: 0.022000 % 21.782178
8 delta: 0.021000 % 20.792079
9 delta: 0.021000 % 20.792079
10 delta: 0.021000 % 20.792079
11 delta: 0.021000 % 20.792079
12 delta: 0.023000 % 22.549020
13 delta: 0.021000 % 20.792079
14 delta: 0.023000 % 22.549020
15 delta: 0.022000 % 21.782178
16 delta: 0.023000 % 22.549020
17 delta: 0.023000 % 22.549020
18 delta: 0.023000 % 22.549020
19 delta: 0.021000 % 20.792079
20 delta: 0.023000 % 22.330097
21 delta: 0.021000 % 20.792079
22 delta: 0.022000 % 21.782178
23 delta: 0.022000 % 21.782178
24 delta: 0.021000 % 20.792079
25 delta: 0.023000 % 22.549020
26 delta: 0.021000 % 20.792079
27 delta: 0.022000 % 21.568627
28 delta: 0.022000 % 21.568627
29 delta: 0.024000 % 23.076923
30 delta: 0.022000 % 21.782178
31 delta: 0.021000 % 20.792079
32 delta: 0.022000 % 21.782178
33 delta: 0.023000 % 22.549020
34 delta: 0.022000 % 21.782178
35 delta: 0.022000 % 21.782178
36 delta: 0.022000 % 21.782178
37 delta: 0.022000 % 21.782178
38 delta: 0.022000 % 21.782178
39 delta: 0.021000 % 20.792079
40 delta: 0.022000 % 21.782178
41 delta: 0.021000 % 20.792079
42 delta: 0.022000 % 21.782178
43 delta: 0.022000 % 21.782178
44 delta: 0.022000 % 21.782178
45 delta: 0.020000 % 19.801980
46 delta: 0.022000 % 21.782178
47 delta: 0.023000 % 22.549020
48 delta: 0.022000 % 21.782178
49 delta: 0.022000 % 21.782178
50 delta: 0.022000 % 21.782178
51 delta: 0.023000 % 22.549020
52 delta: 0.022000 % 21.782178
53 delta: 0.022000 % 21.782178
54 delta: 0.022000 % 21.782178
55 delta: 0.021000 % 20.792079
56 delta: 0.022000 % 21.782178
57 delta: 0.022000 % 21.782178
58 delta: 0.022000 % 21.782178
59 delta: 0.022000 % 21.782178
60 delta: 0.021000 % 20.792079
61 delta: 0.020000 % 20.000000
62 delta: 0.022000 % 21.782178
63 delta: 0.022000 % 21.782178
64 delta: 0.021000 % 20.792079
65 delta: 0.022000 % 21.782178
66 delta: 0.021000 % 20.792079
67 delta: 0.022000 % 21.782178
68 delta: 0.022000 % 21.782178
69 delta: 0.021000 % 20.792079
70 delta: 0.022000 % 21.782178
71 delta: 0.022000 % 21.782178
72 delta: 0.022000 % 21.782178
73 delta: 0.022000 % 21.782178
74 delta: 0.022000 % 21.782178
75 delta: 0.022000 % 21.782178
76 delta: 0.021000 % 20.792079
77 delta: 0.022000 % 21.782178
78 delta: 0.022000 % 21.782178
79 delta: 0.022000 % 21.782178
80 delta: 0.023000 % 22.549020
81 delta: 0.022000 % 21.782178
82 delta: 0.022000 % 21.782178
83 delta: 0.023000 % 22.549020
84 delta: 0.022000 % 21.782178
85 delta: 0.023000 % 22.549020
86 delta: 0.022000 % 21.782178
87 delta: 0.022000 % 21.782178
88 delta: 0.022000 % 21.782178
89 delta: 0.022000 % 21.782178
90 delta: 0.022000 % 21.782178
91 delta: 0.023000 % 22.549020
92 delta: 0.022000 % 21.782178
93 delta: 0.022000 % 21.782178
94 delta: 0.022000 % 21.782178
95 delta: 0.022000 % 21.782178
96 delta: 0.022000 % 21.782178
97 delta: 0.021000 % 20.792079
98 delta: 0.022000 % 21.782178
99 delta: 0.022000 % 21.782178
100 delta: 0.022000 % 21.568627
average percents is: 21.61547195

Вывод: обращение к локальным переменным на ~20% быстрее, чем к полям модуля.
Локальные переменные — наши друзья.

Вопросы производительности

Posted in Uncategorized by ilovelua on 11 сентября, 2012

Стало мне тут интересно, насколько в модулях Lua передача таблицы в качестве параметра в функцию быстрее, чем поиск таблицы по имени внутри модуля. Для этого был сооружен тестовый модуль TestModule.lua:

module('TestModule', package.seeall)

testTable = {}

local count = 1000000

-- добавляем в модуль полей, чтобы поиск осуществлялся не в пустой таблице
for i = 1, 100 do
    _M['testTable' .. i] = i
end

function fillTestTableStraight()
    local f = function(i)
        testTable[i] = i
    end

    for i = 1, count do
        f(i)
    end
end

function fillTestTableFromStack()
    local f = function(t, i)
        t[i] = i
    end

    for i = 1, count do
        f(testTable, i)
    end
end

А вот код теста:

local TestModule = require('TestModule')

function test()
    local t = os.clock ()
    TestModule.fillTestTableStraight()
    local t1 = os.clock () - t

    t = os.clock ()
    TestModule.fillTestTableFromStack()
    local t2 = os.clock () - t

    return t1, t2
end

local percents = {}

for i = 1, 100 do
    local t1, t2 = test()
    local delta = string.format('%.6f', t1 - t2)
    local p = string.format('%.6f', ((t1 - t2) / t1) * 100)

    print(i, 'delta:', delta, '%', p)

    table.insert(percents, p)
end

local totalPercents = 0

for i, percent in ipairs(percents) do
    totalPercents = totalPercents + percent
end

print('average percents is:', totalPercents / #percents)

Вот результаты:

1 delta: 0.038000 % 14.728682
2 delta: 0.009000 % 3.913043
3 delta: 0.010000 % 4.329004
4 delta: 0.008000 % 3.508772
5 delta: 0.008000 % 3.508772
6 delta: 0.008000 % 3.508772
7 delta: 0.007000 % 3.070175
8 delta: 0.007000 % 3.083700
9 delta: 0.007000 % 3.070175
10 delta: 0.007000 % 3.083700
11 delta: 0.008000 % 3.508772
12 delta: 0.008000 % 3.508772
13 delta: 0.008000 % 3.508772
14 delta: 0.008000 % 3.508772
15 delta: 0.007000 % 3.083700
16 delta: 0.003000 % 1.315789
17 delta: 0.016000 % 6.779661
18 delta: 0.007000 % 3.070175
19 delta: 0.008000 % 3.508772
20 delta: 0.007000 % 3.083700
21 delta: 0.010000 % 4.366812
22 delta: 0.008000 % 3.508772
23 delta: 0.008000 % 3.508772
24 delta: 0.007000 % 3.070175
25 delta: 0.009000 % 3.930131
26 delta: 0.011000 % 4.761905
27 delta: 0.008000 % 3.508772
28 delta: 0.007000 % 3.083700
29 delta: 0.006000 % 2.643172
30 delta: 0.008000 % 3.508772
31 delta: 0.008000 % 3.508772
32 delta: 0.007000 % 3.070175
33 delta: 0.007000 % 3.083700
34 delta: 0.008000 % 3.508772
35 delta: 0.007000 % 3.083700
36 delta: 0.008000 % 3.508772
37 delta: 0.007000 % 3.083700
38 delta: 0.008000 % 3.508772
39 delta: 0.008000 % 3.508772
40 delta: 0.007000 % 3.083700
41 delta: 0.008000 % 3.508772
42 delta: 0.007000 % 3.056769
43 delta: 0.008000 % 3.508772
44 delta: 0.007000 % 3.083700
45 delta: 0.009000 % 3.930131
46 delta: 0.007000 % 3.083700
47 delta: 0.007000 % 3.083700
48 delta: 0.009000 % 3.947368
49 delta: 0.008000 % 3.508772
50 delta: 0.009000 % 3.947368
51 delta: 0.008000 % 3.508772
52 delta: 0.008000 % 3.508772
53 delta: 0.008000 % 3.508772
54 delta: 0.007000 % 3.083700
55 delta: 0.007000 % 3.083700
56 delta: 0.008000 % 3.508772
57 delta: 0.008000 % 3.508772
58 delta: 0.008000 % 3.508772
59 delta: 0.008000 % 3.508772
60 delta: 0.009000 % 3.947368
61 delta: 0.008000 % 3.508772
62 delta: 0.007000 % 3.083700
63 delta: 0.009000 % 3.930131
64 delta: 0.006000 % 2.643172
65 delta: 0.007000 % 3.070175
66 delta: 0.008000 % 3.508772
67 delta: 0.008000 % 3.508772
68 delta: 0.008000 % 3.508772
69 delta: 0.006000 % 2.643172
70 delta: 0.010000 % 4.366812
71 delta: 0.008000 % 3.508772
72 delta: 0.007000 % 3.083700
73 delta: 0.009000 % 3.947368
74 delta: 0.008000 % 3.508772
75 delta: 0.008000 % 3.508772
76 delta: 0.007000 % 3.083700
77 delta: 0.009000 % 3.930131
78 delta: 0.008000 % 3.524229
79 delta: 0.009000 % 3.947368
80 delta: 0.009000 % 3.947368
81 delta: 0.008000 % 3.508772
82 delta: 0.009000 % 3.947368
83 delta: 0.008000 % 3.508772
84 delta: 0.007000 % 3.083700
85 delta: 0.007000 % 3.083700
86 delta: 0.007000 % 3.083700
87 delta: 0.008000 % 3.508772
88 delta: 0.008000 % 3.508772
89 delta: 0.007000 % 3.083700
90 delta: 0.007000 % 3.083700
91 delta: 0.008000 % 3.508772
92 delta: 0.006000 % 2.620087
93 delta: 0.044000 % 16.666667
94 delta: 0.008000 % 3.508772
95 delta: 0.010000 % 4.347826
96 delta: 0.008000 % 3.508772
97 delta: 0.007000 % 3.083700
98 delta: 0.008000 % 3.508772
99 delta: 0.008000 % 3.508772
100 delta: 0.008000 % 3.508772
average percents is: 3.6979102

Я несколько раз запускал этот тест, и результаты крутятся около 3.5%.

Ну, разница, понятно, не велика. Однако она есть. Чтобы почувствовать, насколько плох поиск по ключу в цикле, нужно воспользоваться локальными переменными. Поменяем в модуле TestModule функцию fillTestTableFromStack():

function fillTestTableFromStack()
    local f = function(t, i)
        t[i] = i
    end

    local tbl = testTable

    for i = 1, count do
        f(tbl, i)
    end
end

Результаты впечатляют гораздо сильнее:
1 delta: 0.037000 % 14.682540
2 delta: 0.038000 % 16.379310
3 delta: 0.033000 % 14.473684
4 delta: 0.037000 % 16.086957
5 delta: 0.033000 % 14.601770
6 delta: 0.037000 % 16.157205
7 delta: 0.036000 % 15.789474
8 delta: 0.033000 % 14.537445
9 delta: 0.033000 % 14.601770
10 delta: 0.035000 % 15.486726
11 delta: 0.035000 % 15.418502
12 delta: 0.032000 % 14.096916
13 delta: 0.034000 % 14.977974
14 delta: 0.034000 % 15.044248
15 delta: 0.033000 % 14.666667
16 delta: 0.035000 % 15.418502
17 delta: 0.035000 % 15.418502
18 delta: 0.030000 % 13.043478
19 delta: 0.036000 % 15.789474
20 delta: 0.035000 % 15.418502
21 delta: 0.034000 % 15.044248
22 delta: 0.034000 % 15.111111
23 delta: 0.036000 % 15.789474
24 delta: 0.035000 % 15.418502
25 delta: 0.035000 % 15.418502
26 delta: 0.035000 % 15.418502
27 delta: 0.035000 % 15.418502
28 delta: 0.034000 % 15.044248
29 delta: 0.034000 % 15.044248
30 delta: 0.036000 % 15.859031
31 delta: 0.034000 % 15.044248
32 delta: 0.035000 % 15.418502
33 delta: 0.035000 % 15.418502
34 delta: 0.033000 % 14.666667
35 delta: 0.034000 % 15.044248
36 delta: 0.034000 % 15.044248
37 delta: 0.034000 % 15.044248
38 delta: 0.035000 % 15.418502
39 delta: 0.035000 % 15.418502
40 delta: 0.033000 % 14.666667
41 delta: 0.034000 % 15.044248
42 delta: 0.035000 % 15.418502
43 delta: 0.036000 % 15.789474
44 delta: 0.035000 % 15.418502
45 delta: 0.032000 % 14.159292
46 delta: 0.033000 % 14.666667
47 delta: 0.035000 % 15.418502
48 delta: 0.035000 % 15.418502
49 delta: 0.036000 % 15.789474
50 delta: 0.036000 % 15.859031
51 delta: 0.035000 % 15.418502
52 delta: 0.034000 % 15.044248
53 delta: 0.037000 % 16.228070
54 delta: 0.036000 % 15.789474
55 delta: 0.037000 % 16.228070
56 delta: 0.035000 % 15.418502
57 delta: 0.035000 % 15.486726
58 delta: 0.034000 % 15.044248
59 delta: 0.034000 % 15.044248
60 delta: 0.036000 % 15.789474
61 delta: 0.034000 % 15.044248
62 delta: 0.037000 % 16.228070
63 delta: 0.034000 % 15.044248
64 delta: 0.035000 % 15.486726
65 delta: 0.036000 % 15.859031
66 delta: 0.034000 % 15.044248
67 delta: 0.035000 % 15.350877
68 delta: 0.037000 % 16.228070
69 delta: 0.034000 % 15.044248
70 delta: 0.034000 % 15.111111
71 delta: 0.037000 % 16.228070
72 delta: 0.036000 % 15.859031
73 delta: 0.037000 % 16.228070
74 delta: 0.036000 % 15.859031
75 delta: 0.035000 % 15.418502
76 delta: 0.036000 % 15.859031
77 delta: 0.036000 % 15.789474
78 delta: 0.034000 % 15.044248
79 delta: 0.034000 % 15.044248
80 delta: 0.035000 % 15.418502
81 delta: 0.035000 % 15.486726
82 delta: 0.035000 % 15.418502
83 delta: 0.035000 % 15.486726
84 delta: 0.034000 % 15.044248
85 delta: 0.037000 % 16.228070
86 delta: 0.034000 % 15.044248
87 delta: 0.035000 % 15.418502
88 delta: 0.034000 % 15.044248
89 delta: 0.034000 % 15.044248
90 delta: 0.035000 % 15.418502
91 delta: 0.040000 % 17.167382
92 delta: 0.038000 % 16.593886
93 delta: 0.034000 % 15.044248
94 delta: 0.035000 % 15.418502
95 delta: 0.034000 % 15.044248
96 delta: 0.034000 % 15.044248
97 delta: 0.033000 % 14.666667
98 delta: 0.036000 % 15.789474
99 delta: 0.038000 % 16.593886
100 delta: 0.034000 % 15.044248
average percents is: 15.36543997

Вывод: локальные переменные — наши друзья.