Sleep(), wait()
Sleep(), wait()
Is there a way to make a code block sleep for a few tick, ms?
- os.clock is not available in factorio
- I could put some code in the event.on_tick and ignore most ticks. But intensive for the game ?
- os.clock is not available in factorio
- I could put some code in the event.on_tick and ignore most ticks. But intensive for the game ?
Re: Sleep(), wait()
The simplest way would probably be
function on_tick(event)
if event.tick % WAIT_TICKS == 0 then
--do code here
end
end
function on_tick(event)
if event.tick % WAIT_TICKS == 0 then
--do code here
end
end
Re: Sleep(), wait()
Code: Select all
function on_tick(event)
if global.tickswait < 1 then
--do code here
else
global.tickswait = global.tickswait - 1 -- 60 ticks pro second
end
end
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
"if event.tick % WAIT_TICKS == 0" is way less expensive, performance-wise.darkfrei wrote:Don't forget to create it.Code: Select all
function on_tick(event) if global.tickswait < 1 then --do code here else global.tickswait = global.tickswait - 1 -- 60 ticks pro second end end
Re: Sleep(), wait()
No it's not. That's about 6 times slower than doing the decrementing counter.cpeosphoros wrote:"if event.tick % WAIT_TICKS == 0" is way less expensive, performance-wise.darkfrei wrote:Don't forget to create it.Code: Select all
function on_tick(event) if global.tickswait < 1 then --do code here else global.tickswait = global.tickswait - 1 -- 60 ticks pro second end end
If you want to get ahold of me I'm almost always on Discord.
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
Wow! Good to know. Going to switch to it then..Rseding91 wrote:No it's not. That's about 6 times slower than doing the decrementing counter.cpeosphoros wrote:"if event.tick % WAIT_TICKS == 0" is way less expensive, performance-wise.darkfrei wrote:Don't forget to create it.Code: Select all
function on_tick(event) if global.tickswait < 1 then --do code here else global.tickswait = global.tickswait - 1 -- 60 ticks pro second end end
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
Ok, call me a stubborn OCD'ed person , but I went and time profiled it.
This is the harness:
And these the results (edited for readability):
First column values are time, in milliseconds.
For the larger ranges, moduling performed about three times as fast as counting down, when doing exactly the same work (in this case, incrementing a global var).
Otoh, unless you have literally hundreds of thousands mods, that will do practically no noticeable difference at all, performance-wise.
This is the harness:
Code: Select all
require "LOGGER"
local function tickCounter(tick)
global.tickswait = global.tickswait - 1
if global.tickswait == 0 then
global.tickswait = 60
global.counter = global.counter + 1
end
end
local function tickModuler(tick)
if tick % 60 == 0 then
global.counter = global.counter + 1
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 runCounter()
global.tickswait = 60
LOGGER.log("Starting counters")
doCounter(tickCounter, 1200, "Counted")
doCounter(tickModuler, 1200, "Moduled")
doCounter(tickCounter, 12000, "Counted")
doCounter(tickModuler, 12000, "Moduled")
doCounter(tickCounter, 120000, "Counted")
doCounter(tickModuler, 120000, "Moduled")
global.tickswait = nil
global.counter = nil
end
local function onTick(event)
if not done then
runCounter()
end
script.on_event(defines.events.on_tick, onTick)
Code: Select all
00000.283: 85:51:12.32: Counted 20
00000.179: 85:51:12.32: Moduled 20
00002.038: 85:51:12.32: Counted 200
00000.612: 85:51:12.32: Moduled 200
00018.513: 85:51:12.32: Counted 2000
00005.653: 85:51:12.32: Moduled 2000
For the larger ranges, moduling performed about three times as fast as counting down, when doing exactly the same work (in this case, incrementing a global var).
Otoh, unless you have literally hundreds of thousands mods, that will do practically no noticeable difference at all, performance-wise.
Re: Sleep(), wait()
Dividing ist difficult. It's better to multiply, or adding something. For example adding -1.
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
The problem is apparently not in the math operation, but In table accessing. Doing some caching for global.tick, etc shaves some milliseconds off, but the module approach is still cheaper, due to no table access.darkfrei wrote:Dividing ist difficult. It's better to multiply, or adding something. For example adding -1.
Re: Sleep(), wait()
Could you try profiling another 'countdown' code which is something like this:cpeosphoros wrote:Ok, call me a stubborn OCD'ed person , but I went and time profiled
For the larger ranges, moduling performed about three times as fast as counting down, when doing exactly the same work (in this case, incrementing a global var).
Otoh, unless you have literally hundreds of thousands mods, that will do practically no noticeable difference at all, performance-wise.
Code: Select all
function check_action()
if not game.tick == global.action_tick then return end
global.action_tick = game.tick + 300 --Do the action every 300 ticks
do_something()
end
- bobingabout
- Smart Inserter
- Posts: 7352
- Joined: Fri May 09, 2014 1:01 pm
- Contact:
Re: Sleep(), wait()
I've done assembly programming before.cpeosphoros wrote:"if event.tick % WAIT_TICKS == 0" is way less expensive, performance-wise.
what's the difference between ==, ~=, >, <, >= and <= for descissions?
Well... when you actually look down at the raw machine code produced... one of these comparator operators of an if block boils down to...
Subtract one number from the other, Then check the Carry flag, or Zero flag.
Keep in mind that > is the opposite of <=, so asking "A >= B" is the same as asking if "NOT (A < B)", and you've basically reduced those 6 checks to 3 "if check, then do this, else do that" checks.
A > or < check is basically subtracting one number from the other and performing a Carry bit check. Subtracting the larger number from the smaller number will set the carry flag, if this isn't the case (They're the same, or you subtracted the smaller from the larger) then the carry flag is not set. So to perform a greater than check, you subtract the number you think is bigger from the number you think is smaller. If the carry flag gets set, then you are correct, and the number is greater than the other one. If it is false, then it's less than or equal to. swapping around the then, and else (internally remember) basically changes your greater than check to a less than or equal to check, so they're the same check. Swapping the two numbers around (subtracting the one you think is smaller from the one you think is larger) will perform the less than vs greater than or equal to check instead. So just doing this one thing can perform all 4 of these checks, the only thing that changes is which number is subtracted from the other, then do you perform the action if the carry is set, or not?
Equal to is similar, subtract any one from the other, and check the zero flag. this will only ever be set if both numbers were the same. Again, you can do something If it's true, or something if it's not true, so swap these around, and you have a not equal to check.
So, internally, this is what each check looks like:
(Depending on system, might be some memory copy to working register instructions here)
subtract one number from the other.
check a flag bit.
goto for true.
goto for false.
That's 4 instructions specifically for the check. for all checks. The only thing that changes is which number you subtract from which, and which system flag you check.
I hope I was insightful here.
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
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.Klonan wrote:Could you try profiling another 'countdown' code which is something like this:
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
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
- 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.
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
Ultimately, you are right, of course.bobingabout wrote:So, internally, this is what each check looks like:
(Depending on system, might be some memory copy to working register instructions here)
subtract one number from the other.
check a flag bit.
goto for true.
goto for false.
That's 4 instructions specifically for the check. for all checks. The only thing that changes is which number you subtract from which, and which system flag you check.
I hope I was insightful here.
But Lua - as any other interpreted language - will add some layers of complexity to that.
Put simply, the work of preprocessing code to the Virtual Machine's opcode, and then actually running it on the VM will sometimes upend the traditional thinking we are used to with assembly code, or even with compiled languages.
As an example, with the profiling code I've provided above, I truly expected the naive decrementer to outperform the module operator, as Rseding91 have said before, probably by an entire magnitude order. But the actual decrementing code went on and surprised me by performing almost the same, even a bit slower, than the module.
In the end, however, as a mod writer, I think I shall stick with code readability, maintainability and flexibility, in that order, as what I will choose for the methods of counting. The time difference between the different implementations is almost negligible, in the order of tenths of milliseconds, unless you have thousands of mods.
I think moduling, Klonan's and Nexela's implementations are the best ones for now. Moduling is simple, readable, maitainable and works very fast, without much flexibility. Klonan's and Nexela's each adds one layer of flexibility, without sacrificing much of readability and maintainability, at the cost of some performance, which is still negligible in the order of hundreds or even a couple thousands of mods.
Re: Sleep(), wait()
Awesome findings. Thanks for looking into it.
- cpeosphoros
- Inserter
- Posts: 40
- Joined: Fri Dec 23, 2016 10:57 pm
- Contact:
Re: Sleep(), wait()
You're welcome. You may find those results interesting, too: viewtopic.php?f=25&t=39069Nexela wrote:Awesome findings. Thanks for looking into it.