Skip to content

MacOS reports it has nano-second precision, but it most often doesn't --> Bad nano benchmark results #313

Description

@PragTob

Finally got around to verifying this once I got a hand on a Mac, I'll add more details from my own computer later but the gist is this:

Problem

In whatever circumstance that I don't understand :erlang.monotonic_time()only answers in microsecond precision while acting like nanosecond. The impact is that we get measurements that aren't precise enough and as a result are reporting weird measurements/medians of 0ns or what not.

Here you can see the monotonic_time()behaviour changing, between answering in thousands (microseconds) and precise (nanoseconds)

iex(20)> :erlang.monotonic_time()
-576460662355025000
iex(21)> :erlang.monotonic_time()
-576460661060082000
iex(22)> :erlang.monotonic_time()
-576460660057118000
iex(23)> :erlang.monotonic_time()
-576460658439012000
iex(24)> :erlang.monotonic_time()
-576460657486274000
iex(25)> :erlang.monotonic_time()
-576460656358455000
iex(26)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^CJalynas-MacBook-Pro-2:strings_bench tobi$ iex
Erlang/OTP 22 [erts-10.5.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :erlang.monotonic_time()
-576460750092774257
iex(2)> :erlang.monotonic_time()
-576460748033749614
iex(3)> :erlang.monotonic_time()
-576460746882032373
iex(4)> :erlang.monotonic_time()
-576460745953850985
iex(5)> :erlang.monotonic_time()
-576460744563625901
iex(6)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^CJalynas-MacBook-Pro-2:strings_bench tobi$ iex -S mix
Erlang/OTP 22 [erts-10.5.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :erlang.monotonic_time()
-576460749655663000
iex(2)> :erlang.monotonic_time()
-576460748341135000
iex(3)> :erlang.monotonic_time()
-576460746837166000
iex(4)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^CJalynas-MacBook-Pro-2:strings_bench tobi$ iex
Erlang/OTP 22 [erts-10.5.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :erlang.monotonic_time()
-576460748654849000
iex(2)> :erlang.monotonic_time()
-576460747093918000

and yes, the native time unit claims to be :nanosecond (no change when converting native to nanosecond):


iex(7)> :erlang.convert_time_unit(1000, :native, :nanosecond)
1000
iex(8)> :erlang.monotonic_time()
-576460691963568000
iex(9)> :erlang.monotonic_time()
-576460691066966000

as a result our measurements can look like this and produce very odd results:

0
2000
1000
0
1000
1000
1000
1000
1000
0
1000
0
0
1000
0
1000
0
1000
1000
1000
0
1000
0
1000
1000
0
1000
1000

Name             ips        average  deviation         median         99th %
iodata        1.56 M      642.21 ns   ±330.72%        1000 ns        3000 ns
string        1.39 M      720.55 ns  ±1535.92%           0 ns        3000 ns

Comparison:
iodata        1.56 M
string        1.39 M - 1.12x slower +78.34 ns

Background

See this twitter thread, basically I noticed odd looking benchmarking results in this blog post (a median of 0ns). The benchmark can be found here

Further Debugging

After looking into the Erlang Time Correction docs I hoped that the os_monotonic information might be different but apart from the bad time values it seems to be the same between Mac and my linux machine:

# example from my Linux system, but apart from the time Mac results look the same
iex(2)> :erlang.system_info(:os_monotonic_time_source)
[
  function: :clock_gettime,
  clock_id: :CLOCK_MONOTONIC, 
  resolution: 1000000000,
  extended: :no,
  parallel: :yes,
  time: 5688955479888
]

I also investigated time_warp_mode but results on the mac didn't change.


Opened an issue at Erlang/OTP: https://bugs.erlang.org/browse/ERL-1067

Possible Solutions

  • explicitly don't trust macos and treat it as we'd treat microsecond measurements (repetition and all)
  • make the repeated measurement code more intelligent and "doubtful" when it returns multiples of a thousand and then rather retry and see if it ends up divisble by a thousand again
  • your idea here

Workarounds possible now

  • run benchmarks on linux
  • manually integrate repetition to go over a threshold of ~10 microseconds

Environment

Values obtained from a MBP from ~2014/2015 running 10.14.2 Mojave on an i5 processor

Is this a benchee bug?

Yes and No. Yes the results reported in the end aren't accurate, however I'd say that this time reporting with another precision than reported is a bug in the BEAM or maybe MacOS or I dunno what.

We still need to fix it/make it better as lots of people out there are doing benchmarks on MacOS.

Can I help?

If you have a mac you can run the benchmark and post your results: https://github.com/oestrich/strings_bench

If you could run an iex shell and run :erlang.monotonic_time() a couple of times, kill the session and run it again seeing if the precision changes as seen above, that'd be nice. I'd like to see if this happens all the time/for everything or just specific configurations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions