Last active
December 8, 2023 18:44
-
-
Save ryanwinchester/2176482097224ae3f32c23d53b0c7828 to your computer and use it in GitHub Desktop.
Benchmark Bandit WebSocket Frame Mask
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Mix.install([ | |
{:benchee, "~> 1.2"} | |
]) | |
# ------------------------------------------------------------------------------ | |
# Original code | |
# ------------------------------------------------------------------------------ | |
defmodule Original do | |
# Masking is done @mask_size bits at a time until there is less than that number of bits left. | |
# We then go 32 bits at a time until there is less than 32 bits left. We then go 8 bits at | |
# a time. This yields some significant perforamnce gains for only marginally more complexity | |
@mask_size 512 | |
# Note that masking is an involution, so we don't need a separate unmask function | |
def mask(payload, mask) when bit_size(payload) >= @mask_size do | |
payload | |
|> do_mask(String.duplicate(<<mask::32>>, div(@mask_size, 32)), []) | |
|> Enum.reverse() | |
|> IO.iodata_to_binary() | |
end | |
def mask(payload, mask) do | |
payload | |
|> do_mask(<<mask::32>>, []) | |
|> Enum.reverse() | |
|> IO.iodata_to_binary() | |
end | |
defp do_mask( | |
<<h::unquote(@mask_size), rest::binary>>, | |
<<int_mask::unquote(@mask_size)>> = mask, | |
acc | |
) do | |
do_mask(rest, mask, [<<Bitwise.bxor(h, int_mask)::unquote(@mask_size)>> | acc]) | |
end | |
defp do_mask(<<h::32, rest::binary>>, <<int_mask::32, _mask_rest::binary>> = mask, acc) do | |
do_mask(rest, mask, [<<Bitwise.bxor(h, int_mask)::32>> | acc]) | |
end | |
defp do_mask(<<h::8, rest::binary>>, <<current::8, mask::24, _mask_rest::binary>>, acc) do | |
do_mask(rest, <<mask::24, current::8>>, [<<Bitwise.bxor(h, current)::8>> | acc]) | |
end | |
defp do_mask(<<>>, _mask, acc), do: acc | |
end | |
# ------------------------------------------------------------------------------ | |
# Proposed changes | |
# ------------------------------------------------------------------------------ | |
defmodule Proposed do | |
@spec mask(binary(), pos_integer()) :: binary() | |
def mask(payload, mask) when is_integer(mask) do | |
payload_size = byte_size(payload) | |
count_4_bytes = div(payload_size, 4) | |
mask_repetitions = | |
if rem(payload_size, 4) == 0, do: count_4_bytes, else: count_4_bytes + 1 | |
fit_mask = | |
<<mask::32>> | |
|> :binary.copy(mask_repetitions) | |
|> :binary.part(0, payload_size) | |
:crypto.exor(payload, fit_mask) | |
end | |
end | |
# ------------------------------------------------------------------------------ | |
# Inputs | |
# ------------------------------------------------------------------------------ | |
input_sm = String.duplicate("a", 500) | |
input_md = String.duplicate("a", 500_000) | |
input_lg = String.duplicate("a", 16_000_000) | |
# ------------------------------------------------------------------------------ | |
# Bail if they're not the same. | |
# ------------------------------------------------------------------------------ | |
masked_1 = Original.mask(input_sm, 1234) | |
masked_2 = Proposed.mask(input_sm, 1234) | |
^masked_1 = masked_2 | |
masked_1 = Original.mask(input_md, 1234) | |
masked_2 = Proposed.mask(input_md, 1234) | |
^masked_1 = masked_2 | |
masked_1 = Original.mask(input_lg, 1234) | |
masked_2 = Proposed.mask(input_lg, 1234) | |
^masked_1 = masked_2 | |
# ------------------------------------------------------------------------------ | |
# Benchmarks | |
# ------------------------------------------------------------------------------ | |
# You can test the function, or test the function x1000 times. | |
# Results are the same ratios. | |
Benchee.run( | |
%{ | |
"original" => fn input -> Original.mask(input, 1234) end, | |
"proposed" => fn input -> Proposed.mask(input, 1234) end, | |
# "original x 1000" => fn input -> | |
# for _ <- 1..1000, do: Original.mask(input, 1234) | |
# end, | |
# "proposed x 1000" => fn input -> | |
# for _ <- 1..1000, do: Proposed.mask(input, 1234) | |
# end | |
}, | |
inputs: %{ | |
"sm" => input_sm, | |
"md" => input_md, | |
"lg" => input_lg | |
}, | |
time: 5, | |
memory_time: 2 | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Without the
x1000