Skip to content

Commit 1c589ca

Browse files
authored
Optimize Huffman decoding (#29)
Decode into a tail-recursive binary accumulator instead of rebuilding binaries while unwinding recursion. This is up to 4.2x faster on realistic inputs and reduces measured allocations.
1 parent 7eea90a commit 1c589ca

1 file changed

Lines changed: 10 additions & 8 deletions

File tree

lib/hpax/huffman.ex

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,27 @@ defmodule HPAX.Huffman do
6161
## Decoding
6262

6363
@spec decode(binary()) :: binary()
64-
def decode(binary)
64+
def decode(binary) when is_bitstring(binary) do
65+
decode(binary, <<>>)
66+
end
6567

6668
for {byte_value, bits, bit_count} <- regular_entries do
67-
def decode(<<unquote(bits)::size(unquote(bit_count)), rest::bitstring>>) do
68-
<<unquote(byte_value), decode(rest)::binary>>
69+
defp decode(<<unquote(bits)::size(unquote(bit_count)), rest::bitstring>>, acc) do
70+
decode(rest, <<acc::binary, unquote(byte_value)>>)
6971
end
7072
end
7173

72-
def decode(<<>>) do
73-
<<>>
74+
defp decode(<<>>, acc) do
75+
acc
7476
end
7577

7678
# Use binary syntax for single match context optimization.
77-
def decode(<<padding::bitstring>>) when bit_size(padding) in 1..7 do
79+
defp decode(<<padding::bitstring>>, acc) when bit_size(padding) in 1..7 do
7880
padding_size = bit_size(padding)
7981
<<padding::size(^padding_size)>> = padding
8082

8183
if take_significant_bits(unquote(eos_bits), unquote(eos_bit_count), padding_size) == padding do
82-
<<>>
84+
acc
8385
else
8486
throw({:hpax, {:protocol_error, :invalid_huffman_encoding}})
8587
end
@@ -89,7 +91,7 @@ defmodule HPAX.Huffman do
8991
# above only match a complete code, the empty binary, or up to 7 bits of valid EOS padding).
9092
# This can only happen with a malformed/malicious encoding, since a real encoder never
9193
# produces output with more than 7 trailing bits that aren't a complete code.
92-
def decode(<<_rest::bitstring>>) do
94+
defp decode(<<_rest::bitstring>>, _acc) do
9395
throw({:hpax, {:protocol_error, :invalid_huffman_encoding}})
9496
end
9597

0 commit comments

Comments
 (0)