Why is Enum.map more efficient than Enum.count in Elixir?

I found that counting with Enum.map |> Enum.sum works much faster than Enum.count . But why the function of the built-in counter is not effective?

Compare these two lines:

 elements |> Enum.count(&check/1) elements |> Enum.map(&(if check(&1), do: 1, else: 0)) |> Enum.sum 

Test results with different list lengths:

 len | map(), µs | count(), µs =============================== 10 | 0.67 | 2.55 100 | 5.52 | 8.91 1000 | 59.00 | 73.12 10000 | 642.50 | 734.60 

Source code: https://gist.github.com/artemrizhov/fc146f7ab390f7a807be833099e5cb83

 $ elixir --version Erlang/OTP 19 [erts-8.1] [source-e7be63d] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] Elixir 1.3.4 
+5
source share
1 answer

This is because both Enum.map/2 and Enum.reduce/3 (used by sum ) are highly optimized for lists, and Enum.count/2 has only a common enumerated version.

To add to the confusion, there is an even faster implementation:

 elements |> Enum.filter(&check/1) |> Enum.count 

On my machine, using the modified control parameter that you provided, I get a consistent result:

 len = 10 map: 2.1706 μs count: 7.0754 μs filter: 1.9765 μs len = 100 map: 19.921 μs count: 27.579 μs filter: 14.591 μs len = 1000 map: 168.93 μs count: 178.93 μs filter: 163.43 μs len = 10000 map: 2128.9 μs count: 1822.1 μs filter: 1664.6 μs 

However, both the card and filter versions cause more garbage when they are running, so part of the time can be absorbed by the extended garbage collection time. This is already seen in the tests, since the relative profit between the versions decreases as the length of the list (and the amount of intermediate garbage) increases.

EDIT: I introduced a PR that optimizes the Enum.count/2 function as the fastest https://github.com/elixir-lang/elixir/pull/5567

+11
source

Source: https://habr.com/ru/post/1261361/


All Articles