Benchmarking Erlang's ~application:get_env/3~

Had a bit of a discussion with a coworker this week. We were kicking around whether calling Erlang's application:get_env/3 function repeatedly – like, many times a second – would actually cause a noticeable performance hit.

My gut feeling was it should be fast. The reason being, if you look at what application:get_env/3 actually does (check the source if you like: https://github.com/erlang/otp/blob/ab9a8fee111ee1b16b6b620136379aaece6a29c3/lib/kernel/src/application.erl#L924), it boils down to a lookup in an internal ETS table via application_controller:get_env.

The relevant code from application_controller.erl shows this directly:

get_env(AppName, Key) ->
    NotFound = make_ref(),
    case ets:lookup_element(ac_tab, {env, AppName, Key}, 2, NotFound) of
        NotFound -> undefined;
        Val -> {ok, Val}
    end.

get_env(AppName, Key, Default) ->
    ets:lookup_element(ac_tab, {env, AppName, Key}, 2, Default).

That's straight from application_controller.erl (https://github.com/erlang/otp/blob/ab9a8fee111ee1b16b6b620136379aaece6a29c3/lib/kernel/src/application_controller.erl#L347). An ets:lookup_element is generally a quick ride.

To get a real read on it instead of just guessing, I put together a benchmark. The code's over here if you're curious: https://git.srht/~peixian/get_env_benchmark

The numbers came back looking pretty good, backing up that expectation.

For hitting the same value over and over, it's fast:

--- Running Scenario 1 (Repeated Reads) ---
Scenario 1 (Repeated Reads):
  Iterations: 100000
  Total time: 643 microseconds
  Average time per read: 6.430000 nanoseconds

Averaging in the single-digit nanoseconds per read is quick enough it's unlikely to be your bottleneck in most applications.

Even when you mix in writes to the environment, it holds up. We tested reading, writing a new value, and reading again:

--- Running Scenario 2 (Interspersed Reads/Writes) ---
Scenario 2 (Interspersed Reads/Writes):
  Cycles: 1000
  Average time per read BEFORE write: 50.000000 nanoseconds
  Average time per read AFTER write: 1.000000 nanoseconds

There's a bit of a jump for the read right before a write in these test runs, which is interesting, but the read immediately after the write is back down to minimal nanoseconds. The impact of the write doesn't seem to linger and slow down subsequent reads much at all.

So, based on the code and the benchmark numbers, hitting application:get_env/3 a lot looks perfectly fine. It performs like the quick ETS lookup it is.

Posted: 2025-04-19
Filed Under: erlang