139 lines
4.1 KiB
Elixir
139 lines
4.1 KiB
Elixir
# PersistentVector vs Erlang Tuple vs List benchmarks
|
|
# Run: mix run bench/persistent_vector_bench.exs
|
|
|
|
defmodule PVBench do
|
|
def run do
|
|
sizes = [10, 100, 1_000, 10_000]
|
|
|
|
IO.puts("=" |> String.duplicate(70))
|
|
IO.puts("PersistentVector vs Tuple vs List — Benchmarks")
|
|
IO.puts("=" |> String.duplicate(70))
|
|
|
|
for size <- sizes do
|
|
IO.puts("\n--- Size: #{size} ---\n")
|
|
list = Enum.to_list(1..size)
|
|
tuple = List.to_tuple(list)
|
|
pv = CljElixir.PersistentVector.from_list(list)
|
|
|
|
bench_construction(size, list)
|
|
bench_indexed_access(size, list, tuple, pv)
|
|
bench_append(size, list, tuple, pv)
|
|
bench_update(size, list, tuple, pv)
|
|
bench_iteration(size, list, tuple, pv)
|
|
end
|
|
end
|
|
|
|
defp bench_construction(size, list) do
|
|
{pv_us, _} = :timer.tc(fn ->
|
|
CljElixir.PersistentVector.from_list(list)
|
|
end)
|
|
|
|
{tuple_us, _} = :timer.tc(fn ->
|
|
List.to_tuple(list)
|
|
end)
|
|
|
|
IO.puts(" Construction:")
|
|
IO.puts(" PersistentVector.from_list: #{format_us(pv_us)}")
|
|
IO.puts(" List.to_tuple: #{format_us(tuple_us)}")
|
|
IO.puts(" List (identity): ~0 µs")
|
|
end
|
|
|
|
defp bench_indexed_access(size, list, tuple, pv) do
|
|
indices = for _ <- 1..1000, do: :rand.uniform(size) - 1
|
|
|
|
{pv_us, _} = :timer.tc(fn ->
|
|
Enum.each(indices, fn i -> CljElixir.PersistentVector.pv_nth(pv, i) end)
|
|
end)
|
|
|
|
{tuple_us, _} = :timer.tc(fn ->
|
|
Enum.each(indices, fn i -> elem(tuple, i) end)
|
|
end)
|
|
|
|
{list_us, _} = :timer.tc(fn ->
|
|
Enum.each(indices, fn i -> Enum.at(list, i) end)
|
|
end)
|
|
|
|
IO.puts(" Indexed access (1000 random):")
|
|
IO.puts(" PersistentVector.pv_nth: #{format_us(pv_us)}")
|
|
IO.puts(" elem(tuple, i): #{format_us(tuple_us)}")
|
|
IO.puts(" Enum.at(list, i): #{format_us(list_us)}")
|
|
end
|
|
|
|
defp bench_append(size, _list, tuple, pv) do
|
|
iters = min(1000, size)
|
|
|
|
{pv_us, _} = :timer.tc(fn ->
|
|
Enum.reduce(1..iters, pv, fn x, acc ->
|
|
CljElixir.PersistentVector.pv_conj(acc, x)
|
|
end)
|
|
end)
|
|
|
|
{tuple_us, _} = :timer.tc(fn ->
|
|
Enum.reduce(1..iters, tuple, fn x, acc ->
|
|
:erlang.append_element(acc, x)
|
|
end)
|
|
end)
|
|
|
|
{list_us, _} = :timer.tc(fn ->
|
|
# Prepend (O(1)) — append would be O(n) per op
|
|
Enum.reduce(1..iters, [], fn x, acc -> [x | acc] end)
|
|
end)
|
|
|
|
IO.puts(" Append #{iters} elements:")
|
|
IO.puts(" PersistentVector.pv_conj: #{format_us(pv_us)}")
|
|
IO.puts(" :erlang.append_element: #{format_us(tuple_us)}")
|
|
IO.puts(" [x | list] (prepend): #{format_us(list_us)}")
|
|
end
|
|
|
|
defp bench_update(size, _list, tuple, pv) do
|
|
indices = for _ <- 1..1000, do: :rand.uniform(size) - 1
|
|
|
|
{pv_us, _} = :timer.tc(fn ->
|
|
Enum.each(indices, fn i ->
|
|
CljElixir.PersistentVector.pv_assoc(pv, i, :updated)
|
|
end)
|
|
end)
|
|
|
|
{tuple_us, _} = :timer.tc(fn ->
|
|
Enum.each(indices, fn i ->
|
|
put_elem(tuple, i, :updated)
|
|
end)
|
|
end)
|
|
|
|
IO.puts(" Update (1000 random):")
|
|
IO.puts(" PersistentVector.pv_assoc: #{format_us(pv_us)}")
|
|
IO.puts(" put_elem(tuple, i, v): #{format_us(tuple_us)}")
|
|
IO.puts(" List: N/A (O(n) per update)")
|
|
end
|
|
|
|
defp bench_iteration(size, list, tuple, pv) do
|
|
{pv_us, _} = :timer.tc(fn ->
|
|
cnt = CljElixir.PersistentVector.pv_count(pv)
|
|
Enum.reduce(0..(cnt - 1), 0, fn i, acc ->
|
|
acc + CljElixir.PersistentVector.pv_nth(pv, i)
|
|
end)
|
|
end)
|
|
|
|
{tuple_us, _} = :timer.tc(fn ->
|
|
Enum.reduce(0..(size - 1), 0, fn i, acc ->
|
|
acc + elem(tuple, i)
|
|
end)
|
|
end)
|
|
|
|
{list_us, _} = :timer.tc(fn ->
|
|
Enum.reduce(list, 0, fn x, acc -> acc + x end)
|
|
end)
|
|
|
|
IO.puts(" Iteration (sum all):")
|
|
IO.puts(" PersistentVector by index: #{format_us(pv_us)}")
|
|
IO.puts(" Tuple by index: #{format_us(tuple_us)}")
|
|
IO.puts(" List reduce: #{format_us(list_us)}")
|
|
end
|
|
|
|
defp format_us(us) when us < 1_000, do: "#{us} µs"
|
|
defp format_us(us) when us < 1_000_000, do: "#{Float.round(us / 1_000, 1)} ms"
|
|
defp format_us(us), do: "#{Float.round(us / 1_000_000, 2)} s"
|
|
end
|
|
|
|
PVBench.run()
|