Benchmark ExkPasswd on your system
Mix.install([
{:exk_passwd, "~> 0.2.0"},
{:benchee, "~> 1.5"}
])
Microbenchmarks are local measurements, not API guarantees. Keep runtime, hardware, power mode, and benchmark configuration with any published result.
Single-password generation
configs = %{
default: ExkPasswd.Config.Presets.get(:default),
xkcd: ExkPasswd.Config.Presets.get(:xkcd),
wifi: ExkPasswd.Config.Presets.get(:wifi)
}
Benchee.run(
Map.new(configs, fn {name, config} ->
{Atom.to_string(name), fn -> ExkPasswd.generate(config) end}
end),
warmup: 1,
time: 2,
memory_time: 1
)
Batch versus individual generation
config = ExkPasswd.Config.Presets.get(:default)
Benchee.run(
%{
"individual 1,000" => fn ->
for _ <- 1..1_000, do: ExkPasswd.generate(config)
end,
"buffered batch 1,000" => fn ->
ExkPasswd.Batch.generate_batch(1_000, config)
end
},
warmup: 1,
time: 3,
memory_time: 1
)
Do not assume the batch path wins at every size. Buffer refill cost, config validation, scheduler behavior, and the crypto implementation can change the result. Read the generated report rather than relying on a fixed speedup claim.
The checked report from 2026-07-10 (Apple M4 Max, Elixir 1.20.2, OTP 29.0.3) measured 2.20 ms versus 2.41 ms at 100 outputs and 23.10 ms versus 25.66 ms at 1,000 outputs, favoring the buffered batch by about 9–10%. At 10,000 outputs the individual loop measured 256.28 ms versus 285.50 ms for the batch, so the batch was about 11% slower and allocated about 3% more memory in that scenario.
Dictionary selection
Benchee.run(
%{
"count 4..8" => fn -> ExkPasswd.Dictionary.count_between(4, 8) end,
"select 4..8" => fn -> ExkPasswd.Dictionary.random_word_between(4, 8) end,
"list outputs 4..8" => fn -> ExkPasswd.Dictionary.words_between(4, 8) end
},
warmup: 1,
time: 2,
memory_time: 1
)
Common EFF ranges use precomputed tuples. Uncommon and custom ranges may scan the small by-length index and assemble a candidate tuple, so “every dictionary operation is O(1)” would be inaccurate.
Entropy analysis cost
password = ExkPasswd.generate(config)
Benchee.run(
%{
"generate default" => fn -> ExkPasswd.generate(config) end,
"seen entropy" => fn -> ExkPasswd.Entropy.calculate_seen(config) end,
"full report" => fn -> ExkPasswd.Entropy.calculate(password, config) end
},
warmup: 1,
time: 2,
memory_time: 1
)
Seen-entropy analysis enumerates reachable word outputs so it can detect transform collisions. It is intentionally more work than generating one password.
Repository benchmark suite
From a checkout:
mix bench.password
mix bench.dict
mix bench.batch
mix bench.all
Use CI=true mix bench.all only as a short compile/run smoke test. Its sampling
windows are too short for performance conclusions.