Timer precision - can we improve it?

sometime the timer or say send_after/3’s delay will be a litter bit long(16ms or more?)
If is possible to implement a better one?
I seem remember an answer that we can do it in C level

1 Like

Is your question specific to the timer module, or about timers in general (eg erlang:send_after)?

If your concern is just the performance of the timer module, it is known that it is (was, see below) prone to overload, many timers and/or interval timers with short intervals would slow it down, and an interval timer with interval 0 would even stall it completely.
In OTP 25, the timer module has been largely overhauled, and should perform much better now.

3 Likes

@AstonJ Seem I can’t edit my question any more?

I think both of them, old timer using receive and maybe receive work like send_after?
anyway, have a look this scenario:
I need to execute my code 10 times per second(like timer:apply_interval(100,M,F,A))
but sometime I will get a delay 16ms, and 16ms/100ms may be a little bit long

test code
-module(test).

-compile([export_all, nowarn_export_all]).

send_after(N, After) ->
    send_after(N, After, 0).
send_after(0, _After, Acc) ->
    Acc;
send_after(N, After, Acc) ->
    StartTime = (erlang:monotonic_time(microsecond) + 999) div 1000,
    erlang:send_after(StartTime + After, self(), ?MODULE, [{abs, true}]),
    receive
        ?MODULE ->
            EndTime = (erlang:monotonic_time(microsecond) + 999) div 1000,
            send_after(N - 1, After, max(Acc, EndTime - StartTime - After))
    end.

receive_after(N, After) ->
    receive_after(N, After, 0).
receive_after(0, _After, Acc) ->
    Acc;
receive_after(N, After, Acc) ->
    StartTime = (erlang:monotonic_time(microsecond) + 999) div 1000,
    receive
        after After ->
            EndTime = (erlang:monotonic_time(microsecond) + 999) div 1000,
            receive_after(N - 1, After, max(Acc, EndTime - StartTime - After))
    end.
erl  +sct L0-0C0-0P0-0 +S 1:1
Eshell V13.0.2  (abort with ^G)
1> {test:send_after(10,1000), test:receive_after(10,1000)}.
{17,15}

so, can we improve this 16ms

btw, I want to share my point after I read the RP :rofl::
a race condition
when we got Phoenix to 2 million connections
I write a timer wheel in my gen_server behaviour
every process will manager timer by themselves
it will start timer after handle_xxx
if I need a timer belong to ‘global’(manager by node), just using erlang:*_after or timer
finally, it seem can avoid these question

it’s easy to do implementation and sometime I even thing if erlang need more server template
because something like these is hard to implementation in common module
right? :slightly_smiling_face:

1 Like

What OS are you running these tests on? Am I correct in guessing that it is Windows? When I run your test on Linux/macOS it returns {2,1}, but on Windows it is {16,18}. This is because the interrupt interval on Windows is by default 1000ms / 64 (that is 16ms), so that is the best you are going to get there unless you change that interval.

4 Likes

Oh, you are right, I test on windows(dev on window, prod on linux) :sweat_smile:
how can I find out this value

If I can do test on vmware or cloud server(in linux) to avoid this

btw, I using +sct L0-0C0-0P0-0 +S 1:1 to test function performance
I don’t know if it’s a good vm args for testing
so, if there are some vm args recommend
thanks for your reply :grinning:

1 Like
2 Likes

There’s a time limit on editing posts to help prevent abuse/edit spam etc, however if you want to edit something after this cut-off just PM us the changes and we can make them for you :023:

1 Like

On powershell: {14, 14}
On WLS: {2, 2}
On vmware: {3, 1}

so, maybe WLS can do normal test?

1 Like

@garazdawi if I use erlang:start_timer/4 with {abs, true} and a time in the past, will it still start a timer or is it optimized to bypass that and send the timeout message directly?

1 Like

It will still create a timer.

1 Like

It seems so that the message is sent immediately. (still by means of a timer, as @rickard writes).
The documentation only states that the time must be in a certain interval,
which can be calculated with:

21> StaS = erlang:convert_time_unit(erlang:system_info(start_time), native, second).
-576460752
22> calendar:system_time_to_rfc3339(StaS).
“1951-09-26T01:00:48+01:00”

19> EndS = erlang:convert_time_unit(erlang:system_info(end_time), native, second).
8646911283
20> calendar:system_time_to_rfc3339(EndS).
“2244-01-05T00:48:03+01:00”

I recently started a pet project, for watering our plants. There I need some kind of scheduler
for the watering jobs (open and close the valves),
When I set a timer, I compare the requested time with the current time. If it is in the past, I set the
timer for tomorrow.
Here it is, maybe you find something useful:

My timer definition is in the form hour, minute, second and is converted to the erlang monotonic time.

1 Like

This is not correct. erlang:system_info(start_time) and erlang:system_info(end_time) are Erlang monotonic times, so the calendar time conversions made above are wrong. If you want these times to be converted to Erlang system times, you need to add the result from erlang:time_offset() to the Erlang monotonic time.

However, note that erlang:start_timer() with option {abs, true} expects an absolute Erlang monotonic time.

1 Like

You are right. Sorry, I wanted to help instead of adding confusion.

30> calendar:system_time_to_rfc3339(erlang:convert_time_unit(erlang:system_info(start_time), native, second) + erlang:time_offset(second)).
“2022-07-09T21:59:33+02:00”
31> calendar:system_time_to_rfc3339(erlang:convert_time_unit(erlang:system_info(end_time), native, second) + erlang:time_offset(second)).
“2314-10-19T21:46:48+02:00”

The documentation states that start_time is when the current runtime system started, so this makes more sense (I tried this in a new erl instance).

2 Likes

I wrote a simple global timer for multi-worker processes(GitHub - ErlGameWorld/gTimer: Global timer for multi-worker processes), that because some people said that the timer was working in single-process mode and might have performance problems. I wonder if it would be better to change timer to multi-process worker?

1 Like

In that case, I can think of a small improvement to the current (OTP 25) implementation of interval timer handling that may remedy the OPs issue to some degree… (not sure it will, mind you). I will put up a PR, probably tomorrow, instead of going into lengthy explanations here.

1 Like

The current (overhauled) timer implementation already bypasses the single-process timer server whenever it can safely do so. The parts that do have to go through the timer server have been optimized and streamlined, too. So unless you expect to put some very heavy load on it, I doubt you will get much out of going multi-process.

1 Like

I don’t think it is a good idea to rely on timer precision too much. I may be mistaken, but IIRC Erlang does in fact not guarantee that a timer will fire at or anywhere near the given time (or timeout), only that it won’t fire before that, ie early.

1 Like

I’m agree, but my application scenarios need it, so I want to do better :grinning:
a little bit delay(2,3ms/100ms) is ok and my question solution by

1 Like

and @SisMaker
If my scenarios need a timer(everyone has diff after)
normally, I will open timer(erlang:start_timer or erlang:send_after) for every process
may be this heavy load is normal?

1 Like

In your case, I think it would be better to start a timer inside the process

1 Like