Skip to content

Instantly share code, notes, and snippets.

@jchristgit
Created May 21, 2023 19:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jchristgit/a002d1a4c724e62c72386692396417b3 to your computer and use it in GitHub Desktop.
Save jchristgit/a002d1a4c724e62c72386692396417b3 to your computer and use it in GitHub Desktop.
Benchmarking of Erlang's QLC module in various ways
tab = :ets.new(:test, [:set, :public])
:ets.insert(tab, Enum.map(1..10_000, &{&1, %{id: &1, name: "#{&1}"}}))
qh = :ets.table(tab)
ms = [{{:"$1", :"$2"}, [], [{{:"$1", :"$1", :"$2"}}]}]
qhtraverse = :ets.table(tab, traverse: {:select, ms})
# Elixir queries
qh0 = :qlc.string_to_handle('[{Id, Id, Value} || {Id, Value} <- Handle, Id =:= RequestedId].', [], Handle: qh, RequestedId: 500_000)
qh1a = :qlc.string_to_handle('[{Id, Id, Value} || {Id, Value} <- Handle].', [], Handle: qh, RequestedId: 500_000)
qh1b = :qlc.string_to_handle('[Value || {Id, _, Value} <- Handle, Id =:= RequestedId].', [], Handle: qh1a, RequestedId: 500_000)
qh2a = :qlc.string_to_handle('[{Id, Id, Value} || {Id, Value} <- Handle, Id =:= RequestedId].', [], Handle: qhtraverse, RequestedId: 500_000)
# Erlang queries
nqh0 = :qlctest.qh0(qh, 500_000)
nqh1b = :qlctest.qh1b(qh, 500_000)
nqh2a = :qlctest.qh2a(qhtraverse, 500_000)
# Print query information.
# These _should_ be the same between Elixir and Erlang.
# IO.puts("===== NAIVE (Elixir) =====")
# IO.puts(:qlc.info(qh0))
# IO.puts("===== NAIVE (Erlang) =====")
# IO.puts(:qlc.info(nqh0))
# IO.puts("===== NESTED (Elixir) =====")
# IO.puts(:qlc.info(qh1b))
# IO.puts("===== NESTED (Erlang) =====")
# IO.puts(:qlc.info(nqh1b))
# IO.puts("===== NAIVE, TRAVERSED (Elixir) =====")
# IO.puts(:qlc.info(qh2a))
# IO.puts("===== NAIVE, TRAVERSED (Erlang) =====")
# IO.puts(:qlc.info(nqh2a))
counter = fn _item, acc -> acc + 1 end
Benchee.run(
%{
# Queries coming from `string_to_handle` in Elixir.
"naive" => fn -> :qlc.e(qh0) end,
"nested" => fn -> :qlc.e(qh1b) end,
"naive, traversed" => fn -> :qlc.e(qh2a) end,
"naive, fold" => fn -> :qlc.fold(counter, 0, qh0) end,
"nested, fold" => fn -> :qlc.fold(counter, 0, qh1b) end,
"naive, traversed, fold" => fn -> :qlc.fold(counter, 0, qh2a) end,
# No effect.
# "naive, fold, uncached" => fn -> :qlc.fold(counter, 0, qh0, cache_all: false) end,
# "nested, fold, uncached" => fn -> :qlc.fold(counter, 0, qh1b, cache_all: false) end,
# "naive, traversed, fold, uncached" => fn -> :qlc.fold(counter, 0, qh2a, cache_all: false) end,
# A lot slower.
# "naive, cursor" => fn -> cursor_counter.(qh0) |> Enum.sum() end,
# "nested, cursor" => fn -> cursor_counter.(qh1b) |> Enum.sum() end,
# "naive, traversed, cursor" => fn -> cursor_counter.(qh2a) |> Enum.sum() end,
# Queries coming from the parse transform in Erlang.
"native, naive" => fn -> :qlc.e(nqh0) end,
"native, nested" => fn -> :qlc.e(nqh1b) end,
"native, naive, traversed" => fn -> :qlc.e(nqh2a) end,
"native, naive, fold" => fn -> :qlc.fold(counter, 0, nqh0) end,
"native, nested, fold" => fn -> :qlc.fold(counter, 0, nqh1b) end,
"native, naive, traversed, fold" => fn -> :qlc.fold(counter, 0, nqh2a) end,
# No effect.
# "native, naive, fold, uncached" => fn -> :qlc.fold(counter, 0, nqh0, cache_all: false) end,
# "native, nested, fold, uncached" => fn -> :qlc.fold(counter, 0, nqh1b, cache_all: false) end,
# "native, naive, traversed, fold, uncached" => fn -> :qlc.fold(counter, 0, nqh2a, cache_all: false) end,
# A lot slower.
# "native, naive, cursor" => fn -> cursor_counter.(nqh0) |> Enum.sum() end,
# "native, nested, cursor" => fn -> cursor_counter.(nqh1b) |> Enum.sum() end,
# "native, naive, traversed, cursor" => fn -> cursor_counter.(nqh2a) |> Enum.sum() end
},
memory_time: 2,
time: 2
)
-module(qlctest).
-export([qh0/2, qh1b/2, qh2a/2]).
-include_lib("stdlib/include/qlc.hrl").
% Direct lookup on the handle, with a slight rewrite
qh0(Tab, RequestedId) ->
qlc:q([{Id, Id, Value} || {Id, Value} <- Tab, Id =:= RequestedId]).
% Nested lookup via a second QLC that rewrites results
qh1b(Tab, RequestedId) ->
V1 = qlc:q([{Id, Id, Value} || {Id, Value} <- Tab]),
qlc:q([Value || {Id, _, Value} <- V1, Id =:= RequestedId]).
% Lookup on table rewritten via traverse function
qh2a(Tab, RequestedId) ->
qlc:q([{Id, Id, Value} || {Id, _, Value} <- Tab, Id =:= RequestedId]).
Name ips average deviation median 99th %
native, naive 388190.23 0.00258 ms ±679.57% 0.00188 ms 0.00939 ms
native, naive, fold 378867.15 0.00264 ms ±595.45% 0.00193 ms 0.00732 ms
naive 25145.54 0.0398 ms ±20.92% 0.0373 ms 0.0626 ms
naive, fold 25084.02 0.0399 ms ±24.82% 0.0373 ms 0.0629 ms
native, nested 23.21 43.09 ms ±10.36% 41.71 ms 65.80 ms
native, naive, traversed 23.06 43.36 ms ±9.46% 42.61 ms 59.01 ms
native, naive, traversed, fold 22.97 43.53 ms ±7.96% 42.83 ms 53.73 ms
native, nested, fold 21.53 46.45 ms ±15.48% 43.10 ms 68.19 ms
naive, traversed 1.55 644.14 ms ±2.32% 639.93 ms 665.63 ms
naive, traversed, fold 1.46 684.60 ms ±3.41% 676.03 ms 711.02 ms
nested, fold 0.37 2693.06 ms ±0.00% 2693.06 ms 2693.06 ms
nested 0.36 2776.43 ms ±0.00% 2776.43 ms 2776.43 ms
Comparison:
native, naive 388190.23
native, naive, fold 378867.15 - 1.02x slower +0.00006 ms
naive 25145.54 - 15.44x slower +0.0372 ms
naive, fold 25084.02 - 15.48x slower +0.0373 ms
native, nested 23.21 - 16728.42x slower +43.09 ms
native, naive, traversed 23.06 - 16833.76x slower +43.36 ms
native, naive, traversed, fold 22.97 - 16896.88x slower +43.52 ms
native, nested, fold 21.53 - 18033.17x slower +46.45 ms
naive, traversed 1.55 - 250048.14x slower +644.14 ms
naive, traversed, fold 1.46 - 265755.27x slower +684.60 ms
nested, fold 0.37 - 1045420.38x slower +2693.06 ms
nested 0.36 - 1077784.35x slower +2776.43 ms
Memory usage statistics:
Name Memory usage
native, naive 0.00218 MB
native, naive, fold 0.00218 MB - 1.00x memory usage +0 MB
naive 0.0116 MB - 5.31x memory usage +0.00941 MB
naive, fold 0.0116 MB - 5.31x memory usage +0.00941 MB
native, nested 12.75 MB - 5844.20x memory usage +12.75 MB
native, naive, traversed 12.75 MB - 5845.10x memory usage +12.75 MB
native, naive, traversed, fold 12.75 MB - 5842.74x memory usage +12.75 MB
native, nested, fold 12.75 MB - 5841.85x memory usage +12.74 MB
naive, traversed 346.34 MB - 158723.67x memory usage +346.33 MB
naive, traversed, fold 346.44 MB - 158771.85x memory usage +346.44 MB
nested, fold 1064.72 MB - 487952.83x memory usage +1064.71 MB
nested 1064.87 MB - 488021.91x memory usage +1064.86 MB
Name ips average deviation median 99th %
native, naive 384015.58 0.00260 ms ±1298.11% 0.00194 ms 0.00847 ms
native, naive, fold 372373.43 0.00269 ms ±601.45% 0.00196 ms 0.0110 ms
naive 22170.37 0.0451 ms ±210.01% 0.0386 ms 0.116 ms
naive, fold 20456.88 0.0489 ms ±144.07% 0.0389 ms 0.164 ms
native, nested 388.13 2.58 ms ±26.49% 2.31 ms 5.51 ms
native, nested, fold 351.89 2.84 ms ±34.88% 2.32 ms 6.20 ms
native, naive, traversed, fold 328.18 3.05 ms ±37.97% 2.56 ms 6.19 ms
native, naive, traversed 316.37 3.16 ms ±39.75% 2.61 ms 7.23 ms
naive, traversed, fold 14.49 69.02 ms ±8.71% 66.76 ms 84.71 ms
naive, traversed 13.93 71.80 ms ±27.60% 64.84 ms 165.24 ms
nested, fold 3.77 265.53 ms ±5.41% 265.96 ms 284.93 ms
nested 3.66 273.20 ms ±10.90% 264.14 ms 344.07 ms
Comparison:
native, naive 384015.58
native, naive, fold 372373.43 - 1.03x slower +0.00008 ms
naive 22170.37 - 17.32x slower +0.0425 ms
naive, fold 20456.88 - 18.77x slower +0.0463 ms
native, nested 388.13 - 989.39x slower +2.57 ms
native, nested, fold 351.89 - 1091.31x slower +2.84 ms
native, naive, traversed, fold 328.18 - 1170.14x slower +3.04 ms
native, naive, traversed 316.37 - 1213.82x slower +3.16 ms
naive, traversed, fold 14.49 - 26505.46x slower +69.02 ms
naive, traversed 13.93 - 27574.19x slower +71.80 ms
nested, fold 3.77 - 101969.35x slower +265.53 ms
nested 3.66 - 104914.55x slower +273.20 ms
Memory usage statistics:
Name Memory usage
native, naive 0.00218 MB
native, naive, fold 0.00218 MB - 1.00x memory usage +0 MB
naive 0.0116 MB - 5.31x memory usage +0.00941 MB
naive, fold 0.0116 MB - 5.31x memory usage +0.00941 MB
native, nested 1.26 MB - 575.84x memory usage +1.25 MB
native, nested, fold 1.26 MB - 575.58x memory usage +1.25 MB
native, naive, traversed, fold 1.26 MB - 576.47x memory usage +1.26 MB
native, naive, traversed 1.26 MB - 576.73x memory usage +1.26 MB
naive, traversed, fold 34.62 MB - 15867.99x memory usage +34.62 MB
naive, traversed 34.63 MB - 15870.92x memory usage +34.63 MB
nested, fold 106.96 MB - 49018.93x memory usage +106.96 MB
nested 106.97 MB - 49021.67x memory usage +106.96 MB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment