Files
CljElixir/bench/persistent_vector_bench.exs
2026-03-09 23:09:46 -04:00

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()