Skip to content

Instantly share code, notes, and snippets.

@nickva
Created February 16, 2024 20:54
Show Gist options
  • Save nickva/06f1511b7f9d0bbc2d9a0dfc2d36779e to your computer and use it in GitHub Desktop.
Save nickva/06f1511b7f9d0bbc2d9a0dfc2d36779e to your computer and use it in GitHub Desktop.
Apache CouchDB ddoc_cache_lru benchmark
% Benchmark module for ddoc_cache_lru
%
% Benchmark opening and closing (evicting) a bunch of random dbs/ddocs.
%
% Times are measured in microseconds. Best (minimum) results are returned for each
% of the N cases.
%
-module(ddoc_cache_bench).
-export([
go/0,
bench/1
]).
% ddoc_cache behavior callbacks
-export([
dbname/1,
ddocid/1,
recover/1,
insert/2
]).
dbname({DbName, _, _}) ->
DbName.
ddocid({_, DDocId, _}) ->
DDocId.
recover({_DbName, DDocId, Rev}) ->
{ok, doc(DDocId, Rev)}.
insert(_, _) ->
ok.
% end of ddoc_cache behavior callbacks
best_of(Tries, Params) ->
Results = [{_, _} = bench(Params) || _ <- lists:seq(1, Tries)],
{OpenTs, CloseTs} = lists:unzip(Results),
{lists:min(OpenTs), lists:min(CloseTs)}.
go() ->
Cases = [
{100, 100, 5},
{1000, 5, 5},
{10000, 1, 1},
{1, 10000, 1},
{1, 1, 10000}
],
GCs0 = gcs(),
io:format("~nDbs DocIds Revs Open(Dt/Key) Close(Dt/Key)~n", []),
lists:foreach(
fun({Dbs, DocIds, Revs} = Params) ->
{OpenT, CloseT} = best_of(5, Params),
io:format("~-6.10B ~-6.10B ~-6.10B ~-6.1f ~-6.1f ~n", [Dbs, DocIds, Revs, OpenT, CloseT])
end,
Cases
),
GCs1 = gcs(),
io:format("~nGC Runs: ~p~n", [GCs1 - GCs0]),
ok.
bench({DbN, DocN, RevN}) ->
application:stop(ddoc_cache),
Keys = gen_keys(DbN, DocN, RevN),
application:start(ddoc_cache, permanent),
TOpen = open(Keys),
timer:sleep(1000),
TClose = close(Keys),
{TOpen, TClose}.
open(Keys) ->
T0 = ts(),
[ddoc_cache_lru:open(Key) || Key <- Keys],
lru_sync(),
T1 = ts(),
(T1 - T0) / length(Keys).
close(Keys) ->
DbSet = sets:from_list([Db || {?MODULE, {Db, _, _}} <- Keys], [{version, 2}]),
Dbs = sets:to_list(DbSet),
T0 = ts(),
[gen_server:cast(ddoc_cache_lru, {do_evict, Db}) || Db <- Dbs],
lru_sync(),
T1 = ts(),
(T1 - T0) / length(Keys).
lru_sync() ->
% Insert one more random key to force a synchronous gen_server start call
Db = hexbin(128),
DocId = hexbin(1),
Rev = hexbin(1),
{ok, Pid} = ddoc_cache_lru:insert(gen_key(Db, DocId, Rev), hexbin(1)),
true = is_pid(Pid),
ok.
gen_keys(DbN, DocIdN, RevN) ->
Dbs = [hexbin(50) || _ <- lists:seq(1, DbN)],
DocIds = [hexbin(50)|| _ <- lists:seq(1, DocIdN)],
Revs = [hexbin(128) || _ <- lists:seq(1, RevN)],
shuffle([gen_key(Db, DocId, Rev) || Db <- Dbs, DocId <- DocIds, Rev <- Revs]).
gen_key(Db, DocId, Rev) ->
{?MODULE, {Db, DocId, Rev}}.
doc(Id, Rev) ->
{[
{<<"_id">>, Id},
{<<"_rev">>, Rev},
{<<"data">>, [hexbin(16) || _ <- lists:seq(1, 100)]}
]}.
shuffle(List) ->
Paired = [{rand:uniform(), I} || I <- List],
Sorted = lists:sort(Paired),
[I || {_, I} <- Sorted].
hexbin(Size) ->
binary:encode_hex(crypto:strong_rand_bytes(Size div 2)).
ts() ->
erlang:monotonic_time(microsecond).
gcs() ->
{NumberOfGCs, _, _} = statistics(garbage_collection),
NumberOfGCs.
@nickva
Copy link
Author

nickva commented Feb 16, 2024

With maps:

> ddoc_cache_bench:go().

Dbs    DocIds Revs    Open(Dt/Key) Close(Dt/Key)
100    100    5       202.5        11.1
1000   5      5       192.1        21.9
10000  1      1       176.8        65.6
1      10000  1       180.3        30.7
1      1      10000   185.9        32.1

GC Runs: 4733375

With the khash NIF:

> ddoc_cache_bench:go().

Dbs    DocIds Revs    Open(Dt/Key) Close(Dt/Key)
100    100    5       202.2        12.6
1000   5      5       193.2        26.0
10000  1      1       183.1        75.0
1      10000  1       182.8        40.0
1      1      10000   182.8        38.5

GC Runs: 4799318

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment