Proposal: Expose support for :os.perf_counter in System

81 views
Skip to first unread message

Mat Trudel

unread,
Nov 4, 2022, 4:49:38 PM11/4/22
to elixir-lang-core
Using tools such as telemetry in a performant manner requires access to some sort of low cost timing primitive to be able to calculate intervals. The community seems to use a mixture of `System.system_time/0` and/or `System.monotinic_time/0` to accomplish this, though neither of these are built specifically for purpose, and neither are terribly performant on super hot paths [1].

As it turns out, Erlang provides a tool for exactly this purpose, though Elixir doesn't expose it natively. That tool is `:os.perf_counter/1`: https://www.erlang.org/doc/man/os.html#perf_counter-1

It would be good to have first-class access to this in Elixir. Therefore, it is proposed to add the following to System:

* Add `System.perf_counter/0` & `System.perf_counter/1` as thin wrappers around `:os.perf_counter`, in the same manner as is currently done for `System.system_time` et al

* Add support for `perf_counter` to `System.convert_time_unit/3`. This is already supported in the underlying `:erlang.convert_time_unit/3`, it's just a matter of adding a clause to the private `normalize_time_unit` function.

Combined, these will allow for *significantly* faster interval calculation, which should help speed up all sorts of hot telemetry paths.

In practice, `perf_counter` is about twice as fast as `system_time`. A quick comparison (run on an M1 MacBook Air running 1.14/OTP 25.1):

```
Mix.install([{:benchee, "~> 1.0", only: :dev}])

Benchee.run(%{
  system_time: &System.system_time/0,
  monotonic_time: &System.monotonic_time/0,
  perf_counter: &:os.perf_counter/0
})

Name                     ips        average  deviation         median         99th %
perf_counter         54.03 M       18.51 ns   ±439.35%       18.33 ns       20.01 ns
monotonic_time       28.10 M       35.58 ns  ±2758.64%       33.30 ns       41.70 ns
system_time          23.44 M       42.67 ns  ±8964.02%       37.50 ns       45.90 ns

Comparison:
perf_counter         54.03 M
monotonic_time       28.10 M - 1.92x slower +17.08 ns
system_time          23.44 M - 2.31x slower +24.16 ns
```

If folks are onboard I can get this implemented immediately.

m.

Austin Ziegler

unread,
Nov 4, 2022, 7:05:50 PM11/4/22
to elixir-l...@googlegroups.com
I just implemented some telemetry in a library that should be usable in hot paths (but likely has more expensive costs than the telemetry path) and I didn’t use any of the Elixir wrappers to the Erlang module functions, but called them directly.


Do we need this?

-a

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/9d4111e5-4799-4781-bbf1-43dae14dcb94n%40googlegroups.com.


--

Mat Trudel

unread,
Nov 4, 2022, 9:29:37 PM11/4/22
to elixir-lang-core
Hey Austin! 

I realize that making all of the relevant calls to Erlang directly is possible (I might even say straightforward), but the problem is that it's not *discoverable*. The intent here is to 'pave the golden path' and help make the existence and usage of perf_counter clear & straightforward for folks, and to do so in the first place they're likely to look (namely alongside the other `*_time` functions in `System`). 

I'm a pretty experienced and extremely curious guy who's been doing this for quite a while, and if `perf_counter` is news to me, it's likely news to a lot of other people as well. The intent of this proposal isn't to make things *possible*, it's to make them *discoverable*. 

Also, neat context-driven start_span / stop_span model! I've been thinking about something similar for my upcoming telemetry work in Bandit and it's encouraging to see prior art for the idea!

m.

Austin Ziegler

unread,
Nov 4, 2022, 10:25:38 PM11/4/22
to elixir-l...@googlegroups.com
On Fri, Nov 4, 2022 at 9:29 PM Mat Trudel <m...@geeky.net> wrote:
Hey Austin! 

I realize that making all of the relevant calls to Erlang directly is possible (I might even say straightforward), but the problem is that it's not *discoverable*. The intent here is to 'pave the golden path' and help make the existence and usage of perf_counter clear & straightforward for folks, and to do so in the first place they're likely to look (namely alongside the other `*_time` functions in `System`). 

I'm a pretty experienced and extremely curious guy who's been doing this for quite a while, and if `perf_counter` is news to me, it's likely news to a lot of other people as well. The intent of this proposal isn't to make things *possible*, it's to make them *discoverable*. 

Fair enough and a good reason. When I was looking at implementing Telemetry (because this was a good opportunity to dig deep to determine how to adopt it for our main application), I looked at how many common packages were using it and no one was calling `System.monotonic_type` or `System.system_time`, despite the inlining.

You’re right that `:os.perf_counter` is news to me, too, but I’m not sure how or whether *for my use case* it would be better to use that or monotonic time, especially as everyone else is using monotonic time. For the very low level stuff that you’re doing with Thousand Island…the difference might be worth it.
 
Also, neat context-driven start_span / stop_span model! I've been thinking about something similar for my upcoming telemetry work in Bandit and it's encouraging to see prior art for the idea!

I’m not going to claim credit for the model—they are basically the models that `Plug.Telemetry` and `:telemetry.span` use, and similar (in many ways) to what `Télemetria` and `Sybil` do, just honed razor fine for the cases that I felt were worth supporting. I’m also deeply grateful to the Absinthe tests for seeing how tests should be written for using Telemetry.

I think that for the next *major* version (a while from now), I’m going to be shifting it into two or more *different* packages so that there’s a core Erlang version (so that the functionality is available for all Beam users) and then there are utility packages for Elixir packages. In the interim, I’m adding the Telemetry support and will later be adding a `Req` plug-in.

-a

Mat Trudel

unread,
Nov 7, 2022, 2:05:48 PM11/7/22
to elixir-lang-core
Just to close this off, PR was declined: https://github.com/elixir-lang/elixir/pull/12229. Advice is to reach into `:os.perf_counter` for the use case.
Reply all
Reply to author
Forward
0 new messages