Klonan wrote:Could you try profiling another 'countdown' code which is something like this:
All right, I did it. Nexela also sent me a PM with another implementation, which gives the flexibility of performing a different action each time actionTick comes up - in my test harness I went on and performed the same action every time, since wee need uniformity for profiling purposes, but the flexibility Nexela's method provides is very welcome.
I've also done some other implementations of the decrementer, (ab)using some different features of Lua, to see if any of them would give better performance.
Just for testing purposes, I've done two different implementations for Klonan's code. One with not(==) and one with ~=.
Also for testing purposes, I've also came up with a naive implementation of the decremeter, without saving to global. That implementation will not, of course, work in normal gaming environment, since it will not remember the counter's previous value between actual calls.
So, this is the harness (sorry for the long code block - I've omitted the on_tick call, requires, etc - They are the same as in the previous harness):
Code: Select all
local naive = 60
local function doSomething()
global.counter = global.counter + 1
end
local function decrementerCounter1(tick)
global.tickswait = global.tickswait - 1
if global.tickswait == 0 then
global.tickswait = 60
doSomething()
end
end
local function decrementerCounter2(tick)
local tw = global.tickswait - 1
if tw == 0 then
global.tickswait = 60
doSomething()
else
global.tickswait = tw
end
end
local function decrementerCounter3(tick)
local tw = global.tickswait - 1
if tw == 0 then
doSomething()
end
global.tickswait = (tw == 0) and 60 or tw
end
local function incCounter(tw)
if (tw == 0) then
doSomething()
return true
end
return false
end
local function decrementerCounter4(tick)
local tw = global.tickswait - 1
global.tickswait = (incCounter(tw) and 60 or tw)
end
local function klonanCounter1(tick)
if not (tick == global.actionTick) then return end
global.actionTick = tick + 60 --Do the action every 60 ticks
doSomething()
end
local function klonanCounter2(tick)
if tick ~= global.actionTick2 then return end
global.actionTick2 = tick + 60 --Do the action every 60 ticks
doSomething()
end
local function nexelaCounter(tick)
action = global.queue[tick]
if not action then return end
action.toDo()
global.queue[tick] = nil
global.queue[tick + 60] = action --Do the action every 60 ticks
end
local function naiveCounter(tick)
naive = naive - 1
if naive == 0 then
naive = 60
doSomething()
end
end
local function tickModuler(tick)
if tick % 60 == 0 then
doSomething()
end
end
local function doCounter(f, limit, message)
global.counter = 0
for tick = 1, limit do
f(tick)
end
LOGGER.log(message .. " " .. global.counter)
end
local function setGlobals(v)
global.tickswait = 60
global.actionTick = 60
global.actionTick2 = 60
global.queue = {}
global.queue[60] = {toDo = doSomething}
LOGGER.log("Starting counters v=" .. v)
end
local function nilGlobals()
global.tickswait = nil
global.actionTick = nil
global.actionTick2 = nil
global.queue = nil
end
function runCounter()
for _, v in ipairs({1200, 12000, 120000}) do
setGlobals(v)
doCounter(decrementerCounter1, v, "Decrementer 1 Counted -")
doCounter(decrementerCounter2, v, "Decrementer 2 Counted -")
doCounter(decrementerCounter3, v, "Decrementer 3 Counted -")
doCounter(decrementerCounter4, v, "Decrementer 4 Counted -")
doCounter(klonanCounter1, v, "Klonan 1 Counted -")
doCounter(klonanCounter2, v, "Klonan 2 Counted -")
doCounter(nexelaCounter, v, "Nexela Counted -")
doCounter(naiveCounter, v, "Naive Counted -")
doCounter(tickModuler, v, "Moduled -")
end
nilGlobals()
end
And the results (edited for readability):
Code: Select all
00001.073: 102:43:33.28: Decrementer 1 Counted - 20
00000.982: 102:43:33.28: Decrementer 2 Counted - 20
00000.982: 102:43:33.28: Decrementer 3 Counted - 20
00001.045: 102:43:33.28: Decrementer 4 Counted - 20
00000.978: 102:43:33.28: Klonan 1 Counted - 20
00000.521: 102:43:33.28: Klonan 2 Counted - 20
00000.248: 102:43:33.28: Nexela Counted - 20
00000.114: 102:43:33.28: Naive Counted - 20
00000.105: 102:43:33.28: Moduled - 20
00001.846: 102:43:33.28: Decrementer 1 Counted - 200
00001.441: 102:43:33.28: Decrementer 2 Counted - 200
00001.831: 102:43:33.28: Decrementer 3 Counted - 200
00002.342: 102:43:33.28: Decrementer 4 Counted - 200
00001.001: 102:43:33.28: Klonan 1 Counted - 200
00001.008: 102:43:33.28: Klonan 2 Counted - 200
00001.387: 102:43:33.28: Nexela Counted - 200
00000.665: 102:43:33.28: Naive Counted - 200
00000.590: 102:43:33.28: Moduled - 200
00018.336: 102:43:33.28: Decrementer 1 Counted - 2000
00014.130: 102:43:33.28: Decrementer 2 Counted - 2000
00017.860: 102:43:33.28: Decrementer 3 Counted - 2000
00022.216: 102:43:33.28: Decrementer 4 Counted - 2000
00009.666: 102:43:33.28: Klonan 1 Counted - 2000
00009.568: 102:43:33.28: Klonan 2 Counted - 2000
00013.678: 102:43:33.28: Nexela Counted - 2000
00006.288: 102:43:33.28: Naive Counted - 2000
00005.519: 102:43:33.28: Moduled - 2000
Analysis:
- Moduling slightly outperformed even the naive decrementer implementation. The only explanation I can see for that behavior is that probably Lua's implementation of the module operator works internally as a simple subtraction, not a division at all.
- As expected, caching global values is a bit better than accessing them directly (Decremeter 2 vs. Decrementer 1).
- Contrary to Lua performance guides, if-then-else outperformed the ternary and-or construct (Decremeter 3 vs. Decrementer 2), and inserting a function inside the ternary is even uglier (Decrementer 4).
- Klonan's approach is very fast and second best only to directly moduling values, and gives the flexibility to variable cycle lengths. You can decide internally in your cycle code when the next cycle will come up.
- Curiously, not(==) and ~= performed almost exactly the same, which suggests Lua treats both as the same operation, internally.
- Nexela's approach is par with the best decrementer, and gives, on top of what Klonan's provide, also the flexibility of changing behavior for the next cycle.
- Again, unless you have hundred thousands of mods doing that, the time difference between all those approaches is completely negligible, in the order of a couple milliseconds.