Mix.install([{:benchee, "~> 1.0", only: :dev}, {:plug, "~> 1.14"}])
iterations = 10
# Compares the strings with "=="
# Returns 'nil' to minimize time spent on allocating and moving around memory
variable_compare = fn left, right ->
for _ <- 1..iterations, do: left == right
nil
end
# Compares the strings using Plug.Crypto.secure_compare/2
# Also returns 'nil' to avoid memory overhead
secure_compare = fn left, right ->
for _ <- 1..iterations, do: Plug.Crypto.secure_compare(left, right)
nil
end
Benchee.run(
%{
"Variable: 1st Byte" => fn -> variable_compare.("B000000000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 2nd Byte" => fn -> variable_compare.("FB00000000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 5th Byte" => fn -> variable_compare.("F7A5B00000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 10th Byte" => fn -> variable_compare.("F7A53E760B000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 20th Byte" => fn -> variable_compare.("F7A53E760828107A49DB00000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 30th Byte" => fn -> variable_compare.("F7A53E760828107A49D13A6D8C4B0B0000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Variable: 40th Byte" => fn -> variable_compare.("F7A53E760828107A49D13A6D8C4B046674D5A90B", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 1st Byte" => fn -> secure_compare.("B000000000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 2nd Byte" => fn -> secure_compare.("FB00000000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 5th Byte" => fn -> secure_compare.("F7A5B00000000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 10th Byte" => fn -> secure_compare.("F7A53E760B000000000000000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 20th Byte" => fn -> secure_compare.("F7A53E760828107A49DB00000000000000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 30th Byte" => fn -> secure_compare.("F7A53E760828107A49D13A6D8C4B0B0000000000", "F7A53E760828107A49D13A6D8C4B046674D5A902") end,
"Constant: 40th Byte" => fn -> secure_compare.("F7A53E760828107A49D13A6D8C4B046674D5A90B", "F7A53E760828107A49D13A6D8C4B046674D5A902") end
},
time: 15
)
I ran the above benchmark in two versions: First, every run compared the strings only once. That lead to very low iteration times of 2-5 microseconds. I was concerned about the measurement inaccuracy and added for-loops that run the comparison 10 times in every run. That's the code you see above.
Name ips average deviation median 99th %
Variable: 1st Byte 336.31 K 2.97 μs ±1508.56% 2.06 μs 4.10 μs
Variable: 2nd Byte 305.19 K 3.28 μs ±1474.47% 2.47 μs 4.91 μs
Variable: 5th Byte 307.29 K 3.25 μs ±1479.03% 2.43 μs 4.95 μs
Variable: 10th Byte 318.17 K 3.14 μs ±1523.79% 2.26 μs 4.41 μs
Variable: 20th Byte 331.79 K 3.01 μs ±1488.85% 2.13 μs 4.25 μs
Variable: 30th Byte 306.18 K 3.27 μs ±1480.08% 2.44 μs 4.97 μs
Variable: 40th Byte 307.73 K 3.25 μs ±1473.67% 2.42 μs 4.84 μs
Constant: 1st Byte 192.97 K 5.18 μs ±811.50% 4.13 μs 10.11 μs
Constant: 2nd Byte 197.36 K 5.07 μs ±805.35% 4.06 μs 10.05 μs
Constant: 5th Byte 218.06 K 4.59 μs ±798.72% 3.57 μs 7.41 μs
Constant: 10th Byte 196.04 K 5.10 μs ±812.41% 4.06 μs 10.14 μs
Constant: 20th Byte 196.80 K 5.08 μs ±808.10% 4.08 μs 10.10 μs
Constant: 30th Byte 196.46 K 5.09 μs ±807.11% 4.07 μs 10.17 μs
Constant: 40th Byte 200.92 K 4.98 μs ±775.65% 3.98 μs 10.01 μs
Comparison:
Variable: 1st Byte 336.31 K
Variable: 20th Byte 331.79 K - 1.01x slower +0.0406 μs
Variable: 10th Byte 318.17 K - 1.06x slower +0.170 μs
Variable: 40th Byte 307.73 K - 1.09x slower +0.28 μs
Variable: 5th Byte 307.29 K - 1.09x slower +0.28 μs
Variable: 30th Byte 306.18 K - 1.10x slower +0.29 μs
Variable: 2nd Byte 305.19 K - 1.10x slower +0.30 μs
Constant: 5th Byte 218.06 K - 1.54x slower +1.61 μs
Constant: 40th Byte 200.92 K - 1.67x slower +2.00 μs
Constant: 2nd Byte 197.36 K - 1.70x slower +2.09 μs
Constant: 20th Byte 196.80 K - 1.71x slower +2.11 μs
Constant: 30th Byte 196.46 K - 1.71x slower +2.12 μs
Constant: 10th Byte 196.04 K - 1.72x slower +2.13 μs
Constant: 1st Byte 192.97 K - 1.74x slower +2.21 μs
Name ips average deviation median 99th %
Variable: 1st Byte 89.02 K 11.23 μs ±201.22% 9.58 μs 36.17 μs
Variable: 2nd Byte 92.66 K 10.79 μs ±194.45% 9.08 μs 35.60 μs
Variable: 5th Byte 90.73 K 11.02 μs ±192.08% 9.20 μs 35.30 μs
Variable: 10th Byte 87.05 K 11.49 μs ±194.71% 9.57 μs 39.80 μs
Variable: 20th Byte 90.96 K 10.99 μs ±190.70% 9.19 μs 34.37 μs
Variable: 30th Byte 90.38 K 11.06 μs ±192.00% 9.27 μs 35.95 μs
Variable: 40th Byte 87.84 K 11.38 μs ±185.92% 9.34 μs 40.08 μs
Constant: 1st Byte 41.47 K 24.11 μs ±26.11% 22.76 μs 45.22 μs
Constant: 2nd Byte 35.90 K 27.85 μs ±48.86% 25.27 μs 74.90 μs
Constant: 5th Byte 40.41 K 24.75 μs ±30.57% 22.86 μs 53.82 μs
Constant: 10th Byte 40.01 K 25.00 μs ±31.96% 22.82 μs 48.49 μs
Constant: 20th Byte 41.67 K 24.00 μs ±24.34% 22.82 μs 44.77 μs
Constant: 30th Byte 39.53 K 25.29 μs ±37.48% 23.09 μs 56.92 μs
Constant: 40th Byte 39.79 K 25.13 μs ±141.13% 23.15 μs 55.48 μs
Comparison:
Variable: 2nd Byte 92.66 K
Variable: 20th Byte 90.96 K - 1.02x slower +0.20 μs
Variable: 5th Byte 90.73 K - 1.02x slower +0.23 μs
Variable: 30th Byte 90.38 K - 1.03x slower +0.27 μs
Variable: 1st Byte 89.02 K - 1.04x slower +0.44 μs
Variable: 40th Byte 87.84 K - 1.05x slower +0.59 μs
Variable: 10th Byte 87.05 K - 1.06x slower +0.70 μs
Constant: 20th Byte 41.67 K - 2.22x slower +13.20 μs
Constant: 1st Byte 41.47 K - 2.23x slower +13.32 μs
Constant: 5th Byte 40.41 K - 2.29x slower +13.95 μs
Constant: 10th Byte 40.01 K - 2.32x slower +14.20 μs
Constant: 40th Byte 39.79 K - 2.33x slower +14.34 μs
Constant: 30th Byte 39.53 K - 2.34x slower +14.50 μs
Constant: 2nd Byte 35.90 K - 2.58x slower +17.06 μs
Operating System: macOS
CPU Information: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
Number of Available Cores: 12
Available memory: 32 GB
Elixir 1.14.2
Erlang 25.1.1
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 15 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 3.97 min