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