Elixir has incredible time precision! Why only millisecond granularity then?

I just want to say I am incredibly blown away by Elixir’s time precision. Despite it being a multi-threaded system, the timing is quite incredible.

If you run something like this:

defmodule My.TestGenServer do
    use GenServer

    #==================
    # START LINK
    #==================
    def start_link(initial_state) do
        GenServer.start_link(__MODULE__, initial_state, name: __MODULE__)
    end

    #==================
    # INIT FUNCTION
    #==================
    def init(initial_state) do
        #init tasks
        initial_state = %{}
        initial_state = Map.put(initial_state, :date_time, NaiveDateTime.utc_now());
        schedule_work()  # Schedule the first work
        {:ok, initial_state}
    end

    #==================
    # HANDLE INFO
    #==================
    def handle_info(:work, state) do
        # Perform your periodic task here
        {:ok, state} = perform_work(:work, state) # must save the state back again in here
        schedule_work()
        {:noreply, state}
    end

    #==================
    # WORK TASK
    #==================
    defp perform_work(:work, state) do
        #IO.puts("Performing periodic work...")

        #check delta time
        datetime_1 = Map.get(state, :date_time)
        datetime = NaiveDateTime.utc_now()
        diff = NaiveDateTime.diff(datetime, datetime_1, :millisecond) 
        state = Map.put(state, :date_time, datetime) 

        IO.puts("MILLISECONDS DT " <> to_string(diff));

        {:ok, state}
    end

    #==================
    # SCHEDULE TASK
    #==================
    defp schedule_work do
        # Schedule the next work in milliseconds 
        Process.send_after(self(), :work, 3) 
    end

end

You can get incredible reliability on the performance. The output tends to be on my system 1 milliscond greater than the scheduling on average. Eg. If you schedule 3 ms, you may get mostly 4 ms (but this is still amazing for such small time increments). if you schedule 16 ms you may get 17 ms on average.

But it is still quite amazing. Chat GPT says:

Elixir’s accuracy with timers primarily stems from its underlying architecture and the features of the Erlang virtual machine (BEAM) on which it runs. Here are some reasons why Elixir is particularly suited for accurate timing and scheduling:

Erlang/BEAM Scheduler

  • The BEAM virtual machine uses a preemptive scheduling system, allowing it to manage lightweight processes efficiently. This design helps in accurately executing scheduled tasks without significant delays.

The only way I know you can get millisecond accurate timers on Windows is with special multimedia libraries. Android and iOS are impossible as far as I recall last I tried.

It just makes me wonder why we can’t specify smaller increments than 1 ms. Can we say 16.6 ms (ie 60 increments per second)?

Any thoughts on the 1 ms lag that is so consistent it seems? Is that perhaps the 1 ms to do the work each loop and reschedule?

Thanks for any thoughts.

2 Likes

I was curious too, seems it’s an operating system limit, See this other elixir forum thread, which links to this mailing list post, also this erlang forums post

Note this is from 2007, but the same restriction probably applies now:

Yes, it’s nothing Erlang-specific - pretty fundamental actually:
Timeouts can only happen on a system clock interrupt, and they obviously
happen with the frequency of your system clock, which is traditionally
100 Hz on Unix. You can crank it up on most Unixen if you care about
this - I believe some Linux kernel versions used 1000 Hz by default, but
they tended to lose clock interrupts then…

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.

Also Armstrong, on the mailing list

Erlang was designed for programming telephone exchanges - we wanted
response times of milliseconds for simple requests. We also didn’t want
long lived computations to block computations that could be performed
immediately - so each process gets an equal time slice of the CPU.

Which might be indicative that ms-accuracy was good enough for the initial application and so far has been good enough for most other applications too.

I could imagine wanting sub-ms precision for some, particular, C&C industrial controllers but I dont think those have been historically written in Erlang, so I would guess there isn’t much demand.

2 Likes

That is very interesting. Based on the post, it sounds like some of the great time precision is simply a matter of Elixir running on Linux. I will have to try running the same experiment on Windows then. Either way good to know.

What that user suggests about 16 ms minimal intervals or so is what you will typically get on Timers in Windows and iOS and Android unless you use a special multimedia library on Windows for example.

So the performance sounds largely OS defined as well.

Thanks.

1 Like