Skip to content

Instantly share code, notes, and snippets.

@potatosalad
Created February 6, 2019 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save potatosalad/15fcc122b1b5f5c8fd5146f6faf1dd5c to your computer and use it in GitHub Desktop.
Save potatosalad/15fcc122b1b5f5c8fd5146f6faf1dd5c to your computer and use it in GitHub Desktop.
n = 1_000_000
bmap = FooBench.start(FooMap, n)
btup = FooBench.start(FooTuple, n)
Benchee.run(%{
"bmap.read" => fn -> FooBench.readall(bmap) end,
"btup.read" => fn -> FooBench.readall(btup) end
}, memory_time: 5)
Benchee.run(%{
"bmap.clean" => fn -> FooBench.clean(bmap); FooBench.write(bmap) end,
"btup.clean" => fn -> FooBench.clean(btup); FooBench.write(btup) end
}, memory_time: 5)
:ok = FooBench.stop(bmap)
:ok = FooBench.stop(btup)
defmodule FooBench do
defstruct module: nil, store: []
def start(module, number) do
store = module.store_start(number)
%__MODULE__{module: module, store: store}
end
def stop(%__MODULE__{module: module}) do
module.stop()
end
def clean(%__MODULE__{module: module, store: store}) do
module.store_clean(store)
end
def write(%__MODULE__{module: module, store: store}) do
module.store_write(store)
end
def readall(%__MODULE__{module: module, store: store}) do
module.store_readall(store)
end
end
defmodule FooMap do
@stale_after 30_000
@ets :foo_map
defmodule Data do
defstruct core_object: nil, stored_at: nil
end
def start() do
@ets = :ets.new(@ets, [:set, :public, :named_table, {:read_concurrency, true}])
:ok
end
def stop() do
true = :ets.delete(@ets)
:ok
end
def get(core_id) do
stale_time = :erlang.monotonic_time(:millisecond) - @stale_after
case :ets.lookup(@ets, core_id) do
[{^core_id, %Data{core_object: cached_object, stored_at: stored_at}}] when stored_at > stale_time ->
cached_object
[_] ->
:stale
[] ->
nil
end
end
def put(core_id, cached_object) do
true = :ets.insert(@ets, {core_id, %Data{core_object: cached_object, stored_at: :erlang.monotonic_time(:millisecond) + @stale_after}})
:ok
end
def clean(current_monotonic_time \\ :erlang.monotonic_time(:millisecond)) do
# match_spec = [{{:_, :"$1", :_}, [{:"=<", :"$1", current_monotonic_time}], [true]}, {:_, [], [false]}]
match_spec = [{{:_, %{__struct__: FooMap.Data, stored_at: :"$1"}}, [{:"=<", :"$1", current_monotonic_time}], [true]}, {:_, [], [false]}]
count = :ets.select_delete(@ets, match_spec)
count
end
def store_start(number) do
stored_at = :erlang.monotonic_time(:millisecond) + 30_000
store =
for i <- 1..number, into: [] do
key = "#{i}"
value = %{id: key, name: :binary.copy("1234", 16)}
{key, %Data{core_object: value, stored_at: stored_at}}
end
:ok = start()
:ok = store_write(store)
store
end
def store_clean(store) do
expected_length = length(store)
current_time = :erlang.monotonic_time(:millisecond) + 30_000
^expected_length = clean(current_time)
end
def store_write(store) do
true = :ets.insert(@ets, store)
:ok
end
def store_readall(store) do
do_store_readall(store)
end
defp do_store_readall([{key, %{core_object: value}} | rest]) do
^value = __MODULE__.get(key)
do_store_readall(rest)
end
defp do_store_readall([]) do
:ok
end
end
defmodule FooTuple do
@stale_after 30_000
@ets :foo_tuple
defmodule Data do
defstruct core_object: nil, stored_at: nil
end
def start() do
@ets = :ets.new(@ets, [:set, :public, :named_table, {:read_concurrency, true}])
:ok
end
def stop() do
true = :ets.delete(@ets)
:ok
end
def get(core_id) do
stale_time = :erlang.monotonic_time(:millisecond) - @stale_after
case :ets.lookup(@ets, core_id) do
[{^core_id, stored_at, cached_object}] when stored_at > stale_time ->
cached_object
[_] ->
:stale
[] ->
nil
end
end
def put(core_id, cached_object) do
true = :ets.insert(@ets, {core_id, :erlang.monotonic_time(:millisecond) + @stale_after, cached_object})
:ok
end
def clean(current_monotonic_time \\ :erlang.monotonic_time(:millisecond)) do
match_spec = [{{:_, :"$1", :_}, [{:"=<", :"$1", current_monotonic_time}], [true]}, {:_, [], [false]}]
count = :ets.select_delete(@ets, match_spec)
count
end
def store_start(number) do
stored_at = :erlang.monotonic_time(:millisecond) + 30_000
store =
for i <- 1..number, into: [] do
key = "#{i}"
value = %{id: key, name: :binary.copy("1234", 16)}
{key, stored_at, value}
end
:ok = start()
:ok = store_write(store)
store
end
def store_clean(store) do
expected_length = length(store)
current_time = :erlang.monotonic_time(:millisecond) + 30_000
^expected_length = clean(current_time)
end
def store_write(store) do
true = :ets.insert(@ets, store)
:ok
end
def store_readall(store) do
do_store_readall(store)
end
defp do_store_readall([{key, _, value} | rest]) do
^value = __MODULE__.get(key)
do_store_readall(rest)
end
defp do_store_readall([]) do
:ok
end
end
Name ips average deviation median 99th %
btup.read 0.55 1.83 s ±10.86% 1.87 s 2.01 s
bmap.read 0.48 2.09 s ±12.01% 2.05 s 2.36 s
btup.clean 0.85 1.18 s ±1.19% 1.18 s 1.20 s
bmap.clean 0.82 1.21 s ±1.59% 1.21 s 1.24 s
Comparison:
btup.read 0.55
bmap.read 0.48 - 1.14x slower
btup.clean 0.85
bmap.clean 0.82 - 1.03x slower
Memory usage statistics:
Name Memory usage
btup.read 526.43 MB
bmap.read 633.24 MB - 1.20x memory usage
btup.clean 251.81 MB
bmap.clean 289.96 MB - 1.15x memory usage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment