Skip to content

Commit af34130

Browse files
committed
Benchmark faster Base.valid16?
1 parent 13003aa commit af34130

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

bench/base16_valid.exs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
defmodule BaseAlt do
2+
@type decode_case :: :upper | :lower | :mixed
3+
4+
b16_alphabet = ~c"0123456789ABCDEF"
5+
6+
to_mixed_dec =
7+
&Enum.flat_map(&1, fn {encoding, value} = pair ->
8+
if encoding in ?A..?Z do
9+
[pair, {encoding - ?A + ?a, value}]
10+
else
11+
[pair]
12+
end
13+
end)
14+
15+
to_lower_dec =
16+
&Enum.map(&1, fn {encoding, value} = pair ->
17+
if encoding in ?A..?Z do
18+
{encoding - ?A + ?a, value}
19+
else
20+
pair
21+
end
22+
end)
23+
24+
to_decode_list = fn alphabet ->
25+
alphabet = Enum.sort(alphabet)
26+
map = Map.new(alphabet)
27+
{min, _} = List.first(alphabet)
28+
{max, _} = List.last(alphabet)
29+
{min, Enum.map(min..max, &map[&1])}
30+
end
31+
32+
@spec valid16?(binary, case: decode_case) :: boolean
33+
def valid16?(string, opts \\ [])
34+
35+
def valid16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
36+
case Keyword.get(opts, :case, :upper) do
37+
:upper -> validate16upper?(string)
38+
:lower -> validate16lower?(string)
39+
:mixed -> validate16mixed?(string)
40+
end
41+
end
42+
43+
def valid16?(string, _opts) when is_binary(string) do
44+
false
45+
end
46+
47+
upper = Enum.with_index(b16_alphabet)
48+
49+
for {base, alphabet} <- [upper: upper, lower: to_lower_dec.(upper), mixed: to_mixed_dec.(upper)] do
50+
validate_name = :"validate16#{base}?"
51+
valid_char_name = :"valid_char16#{base}?"
52+
53+
{min, decoded} = to_decode_list.(alphabet)
54+
55+
defp unquote(validate_name)(<<>>), do: true
56+
57+
defp unquote(validate_name)(<<c1, c2, rest::binary>>) do
58+
unquote(valid_char_name)(c1) and
59+
unquote(valid_char_name)(c2) and
60+
unquote(validate_name)(rest)
61+
end
62+
63+
defp unquote(validate_name)(<<_char, _rest::binary>>), do: false
64+
65+
@compile {:inline, [{valid_char_name, 1}]}
66+
defp unquote(valid_char_name)(char)
67+
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
68+
do: true
69+
70+
defp unquote(valid_char_name)(_char), do: false
71+
end
72+
end
73+
74+
inputs = [
75+
"small string": Base.encode16("hello world"),
76+
"big string": Base.encode16(:crypto.strong_rand_bytes(1_000_000)),
77+
"invalid string": String.duplicate("1234567890ABCDEF", 10) |> String.replace_trailing("F", "@")
78+
]
79+
80+
Benchee.run(
81+
%{
82+
# as of 30711a724, before optimizing
83+
"main" => &Base.valid16?/1,
84+
"PR" => &BaseAlt.valid16?/1
85+
},
86+
inputs: inputs,
87+
memory_time: 1
88+
)

bench/base16_valid.results.txt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
Operating System: macOS
2+
CPU Information: Apple M1
3+
Number of Available Cores: 8
4+
Available memory: 16 GB
5+
Elixir 1.19.0-dev
6+
Erlang 27.3
7+
8+
Benchmark suite executing with the following configuration:
9+
warmup: 2 s
10+
time: 5 s
11+
memory time: 1 s
12+
reduction time: 0 ns
13+
parallel: 1
14+
inputs: small string, big string, invalid string
15+
Estimated total run time: 48 s
16+
17+
Benchmarking PR with input small string ...
18+
Warning: The function you are trying to benchmark is super fast, making measurements more unreliable!
19+
This holds especially true for memory measurements or when running with hooks.
20+
21+
See: https://github.com/bencheeorg/benchee/wiki/Benchee-Warnings#fast-execution-warning
22+
23+
You may disable this warning by passing print: [fast_warning: false] as configuration options.
24+
25+
Benchmarking PR with input big string ...
26+
Benchmarking PR with input invalid string ...
27+
Benchmarking main with input small string ...
28+
Benchmarking main with input big string ...
29+
Benchmarking main with input invalid string ...
30+
31+
##### With input small string #####
32+
Name ips average deviation median 99th %
33+
PR 8.99 M 111.26 ns ±7891.03% 62.50 ns 141.60 ns
34+
main 4.42 M 226.37 ns ±19885.81% 125 ns 250 ns
35+
36+
Comparison:
37+
PR 8.99 M
38+
main 4.42 M - 2.03x slower +115.11 ns
39+
40+
Memory usage statistics:
41+
42+
Name Memory usage
43+
PR 40 B
44+
main 40 B - 1.00x memory usage +0 B
45+
46+
**All measurements for memory usage were the same**
47+
48+
##### With input big string #####
49+
Name ips average deviation median 99th %
50+
PR 210.69 4.75 ms ±0.45% 4.75 ms 4.81 ms
51+
main 114.54 8.73 ms ±2.65% 8.71 ms 9.22 ms
52+
53+
Comparison:
54+
PR 210.69
55+
main 114.54 - 1.84x slower +3.98 ms
56+
57+
Memory usage statistics:
58+
59+
Name Memory usage
60+
PR 40 B
61+
main 40 B - 1.00x memory usage +0 B
62+
63+
**All measurements for memory usage were the same**
64+
65+
##### With input invalid string #####
66+
Name ips average deviation median 99th %
67+
PR 2.15 M 0.47 μs ±5777.00% 0.42 μs 0.54 μs
68+
main 0.61 M 1.63 μs ±2239.39% 1.17 μs 2.17 μs
69+
70+
Comparison:
71+
PR 2.15 M
72+
main 0.61 M - 3.51x slower +1.17 μs
73+
74+
Memory usage statistics:
75+
76+
Name Memory usage
77+
PR 40 B
78+
main 984 B - 24.60x memory usage +944 B
79+
80+
**All measurements for memory usage were the same**

0 commit comments

Comments
 (0)