Make tictac more efficient by making level1 a map (#441)

* Make tictac more efficient by making level1 a map

Pre-change (1M keys, tree size large):

Generating Keys took 2513 milliseconds
Memory footprint [{total,356732576},{processes,334051328},{processes_used,334044488},{system,22681248},{atom,540873},{atom_used,524383},{binary,1015120},{code,9692859},{ets,721496}]
Generating new tree took 1 milliseconds
Loading tree took 27967 milliseconds
Memory footprint [{total,36733040},{processes,8875472},{processes_used,8875048},{system,27857568},{atom,540873},{atom_used,524449},{binary,6236480},{code,9692859},{ets,721496}]
Exporting tree took 434 milliseconds
Importing tree took 100 milliseconds
Memory footprint [{total,155941512},{processes,123734808},{processes_used,123734384},{system,32206704},{atom,540873},{atom_used,524449},{binary,10401144},{code,9692859},{ets,721496}]
Garbage collect
Memory footprint [{total,39660504},{processes,8257520},{processes_used,8256968},{system,31402984},{atom,540873},{atom_used,524449},{binary,9781760},{code,9692859},{ets,721496}]

Post change:

Generating Keys took 2416 milliseconds
Memory footprint [{total,284678120},{processes,258349528},{processes_used,257758568},{system,26328592},{atom,893161},{atom_used,878150},{binary,1013880},{code,11770188},{ets,774224}]
Generating new tree took 0 milliseconds
Loading tree took 2072 milliseconds
Memory footprint [{total,49957448},{processes,17244856},{processes_used,16653896},{system,32712592},{atom,893161},{atom_used,878216},{binary,7397496},{code,11770188},{ets,774224}]
Exporting tree took 448 milliseconds
Importing tree took 108 milliseconds
Memory footprint [{total,46504880},{processes,11197344},{processes_used,10606384},{system,35307536},{atom,893161},{atom_used,878216},{binary,9992112},{code,11770188},{ets,774224}]
Garbage collect
Memory footprint [{total,47394048},{processes,12223608},{processes_used,11632520},{system,35170440},{atom,893161},{atom_used,878216},{binary,9855008},{code,11770188},{ets,774224}]

* Tidy-up

* Add type

* Remove ++ requiring copy of Acc

Rely on mechanism producing a sorted result, not sorting

* Update src/leveled_tictac.erl

Co-authored-by: Thomas Arts <thomas.arts@quviq.com>

* Update following review

---------

Co-authored-by: Thomas Arts <thomas.arts@quviq.com>
This commit is contained in:
Martin Sumner 2024-09-03 16:34:41 +01:00
parent acf30599e9
commit af0f2bb2cf
2 changed files with 227 additions and 66 deletions

View file

@ -159,7 +159,7 @@
-behaviour(gen_server). -behaviour(gen_server).
-include("include/leveled.hrl"). -include("leveled.hrl").
-export([ -export([
init/1, init/1,
@ -207,23 +207,17 @@
-export([clean_testdir/1]). -export([clean_testdir/1]).
-endif. -endif.
-define(MAX_WORK_WAIT, 300).
-define(MANIFEST_FP, "ledger_manifest"). -define(MANIFEST_FP, "ledger_manifest").
-define(FILES_FP, "ledger_files"). -define(FILES_FP, "ledger_files").
-define(CURRENT_FILEX, "crr").
-define(PENDING_FILEX, "pnd").
-define(SST_FILEX, ".sst"). -define(SST_FILEX, ".sst").
-define(ARCHIVE_FILEX, ".bak"). -define(ARCHIVE_FILEX, ".bak").
-define(SUPER_MAX_TABLE_SIZE, 40000). -define(SUPER_MAX_TABLE_SIZE, 40000).
-define(PROMPT_WAIT_ONL0, 5).
-define(WORKQUEUE_BACKLOG_TOLERANCE, 4). -define(WORKQUEUE_BACKLOG_TOLERANCE, 4).
-define(COIN_SIDECOUNT, 4). -define(COIN_SIDECOUNT, 4).
-define(SLOW_FETCH, 500000). % Log a very slow fetch - longer than 500ms -define(SLOW_FETCH, 500000). % Log a very slow fetch - longer than 500ms
-define(FOLD_SCANWIDTH, 32). -define(FOLD_SCANWIDTH, 32).
-define(ITERATOR_SCANWIDTH, 4). -define(ITERATOR_SCANWIDTH, 4).
-define(ITERATOR_MINSCANWIDTH, 1). -define(ITERATOR_MINSCANWIDTH, 1).
-define(TIMING_SAMPLECOUNTDOWN, 10000).
-define(TIMING_SAMPLESIZE, 100).
-define(SHUTDOWN_LOOPS, 10). -define(SHUTDOWN_LOOPS, 10).
-define(SHUTDOWN_PAUSE, 10000). -define(SHUTDOWN_PAUSE, 10000).
% How long to wait for snapshots to be released on shutdown % How long to wait for snapshots to be released on shutdown

View file

@ -52,8 +52,6 @@
-module(leveled_tictac). -module(leveled_tictac).
-include("include/leveled.hrl").
-export([ -export([
new_tree/1, new_tree/1,
new_tree/2, new_tree/2,
@ -102,10 +100,12 @@
size :: tree_size(), size :: tree_size(),
width :: integer(), width :: integer(),
segment_count :: integer(), segment_count :: integer(),
level1 :: binary(), level1 :: level1_map(),
level2 :: any() % an array - but OTP compatibility level2 :: array:array()
}). }).
-type level1_map() :: #{non_neg_integer() => binary()}.
-type tictactree() :: -type tictactree() ::
#tictactree{}. #tictactree{}.
-type segment48() :: -type segment48() ::
@ -114,6 +114,11 @@
{binary(), integer(), integer(), integer(), binary()}. {binary(), integer(), integer(), integer(), binary()}.
-type tree_size() :: -type tree_size() ::
xxsmall|xsmall|small|medium|large|xlarge. xxsmall|xsmall|small|medium|large|xlarge.
-type bin_extract_fun()
::
fun((term(), term()) ->
{binary(), binary()|{is_hash, non_neg_integer()}}
).
-export_type([tictactree/0, segment48/0, tree_size/0]). -export_type([tictactree/0, segment48/0, tree_size/0]).
@ -137,7 +142,7 @@ new_tree(TreeID) ->
new_tree(TreeID, Size) -> new_tree(TreeID, Size) ->
Width = get_size(Size), Width = get_size(Size),
Lv1Width = Width * ?HASH_SIZE * 8, Lv1Width = Width * ?HASH_SIZE * 8,
Lv1Init = <<0:Lv1Width/integer>>, Lv1Init = to_level1_map(<<0:Lv1Width/integer>>),
Lv2Init = array:new([{size, Width}, {default, ?EMPTY}]), Lv2Init = array:new([{size, Width}, {default, ?EMPTY}]),
#tictactree{treeID = TreeID, #tictactree{treeID = TreeID,
size = Size, size = Size,
@ -159,9 +164,16 @@ export_tree(Tree) ->
L2 = L2 =
lists:foldl(EncodeL2Fun, [], lists:seq(0, Tree#tictactree.width - 1)), lists:foldl(EncodeL2Fun, [], lists:seq(0, Tree#tictactree.width - 1)),
{struct, {struct,
[{<<"level1">>, base64:encode_to_string(Tree#tictactree.level1)}, [{<<"level1">>,
{<<"level2">>, {struct, lists:reverse(L2)}} base64:encode_to_string(
]}. from_level1_map(Tree#tictactree.level1)
)
},
{<<"level2">>,
{struct, lists:reverse(L2)}
}
]
}.
-spec import_tree({struct, list()}) -> tictactree(). -spec import_tree({struct, list()}) -> tictactree().
%% @doc %% @doc
@ -174,8 +186,9 @@ import_tree(ExportedTree) ->
Sizes = lists:map(fun(SizeTag) -> {SizeTag, get_size(SizeTag)} end, Sizes = lists:map(fun(SizeTag) -> {SizeTag, get_size(SizeTag)} end,
?VALID_SIZES), ?VALID_SIZES),
Width = byte_size(L1Bin) div ?HASH_SIZE, Width = byte_size(L1Bin) div ?HASH_SIZE,
{Size, Width} = lists:keyfind(Width, 2, Sizes), {Size, _Width} = lists:keyfind(Width, 2, Sizes),
Width = get_size(Size), %% assert that side is indeed the provided width
true = get_size(Size) == Width,
Lv2Init = array:new([{size, Width}]), Lv2Init = array:new([{size, Width}]),
FoldFun = FoldFun =
fun({X, EncodedL2SegBin}, L2Array) -> fun({X, EncodedL2SegBin}, L2Array) ->
@ -183,15 +196,18 @@ import_tree(ExportedTree) ->
array:set(binary_to_integer(X), L2SegBin, L2Array) array:set(binary_to_integer(X), L2SegBin, L2Array)
end, end,
Lv2 = lists:foldl(FoldFun, Lv2Init, L2List), Lv2 = lists:foldl(FoldFun, Lv2Init, L2List),
#tictactree{treeID = import, garbage_collect(),
#tictactree{
treeID = import,
size = Size, size = Size,
width = Width, width = Width,
segment_count = Width * ?L2_CHUNKSIZE, segment_count = Width * ?L2_CHUNKSIZE,
level1 = L1Bin, level1 = to_level1_map(L1Bin),
level2 = Lv2}. level2 = Lv2
}.
-spec add_kv(tictactree(), term(), term(), fun()) -> tictactree(). -spec add_kv(tictactree(), term(), term(), bin_extract_fun()) -> tictactree().
%% @doc %% @doc
%% Add a Key and value to a tictactree using the BinExtractFun to extract a %% Add a Key and value to a tictactree using the BinExtractFun to extract a
%% binary from the Key and value from which to generate the hash. The %% binary from the Key and value from which to generate the hash. The
@ -200,7 +216,8 @@ import_tree(ExportedTree) ->
add_kv(TicTacTree, Key, Value, BinExtractFun) -> add_kv(TicTacTree, Key, Value, BinExtractFun) ->
add_kv(TicTacTree, Key, Value, BinExtractFun, false). add_kv(TicTacTree, Key, Value, BinExtractFun, false).
-spec add_kv(tictactree(), term(), term(), fun(), boolean()) -spec add_kv(
tictactree(), term(), term(), bin_extract_fun(), boolean())
-> tictactree()|{tictactree(), integer()}. -> tictactree()|{tictactree(), integer()}.
%% @doc %% @doc
%% add_kv with ability to return segment ID of Key added %% add_kv with ability to return segment ID of Key added
@ -215,14 +232,15 @@ add_kv(TicTacTree, Key, Value, BinExtractFun, ReturnSegment) ->
SegLeaf2Upd = SegLeaf2 bxor SegChangeHash, SegLeaf2Upd = SegLeaf2 bxor SegChangeHash,
SegLeaf1Upd = SegLeaf1 bxor SegChangeHash, SegLeaf1Upd = SegLeaf1 bxor SegChangeHash,
UpdatedTree =
replace_segment(
SegLeaf1Upd, SegLeaf2Upd, L1Extract, L2Extract, TicTacTree
),
case ReturnSegment of case ReturnSegment of
true -> true ->
{replace_segment(SegLeaf1Upd, SegLeaf2Upd, {UpdatedTree, Segment};
L1Extract, L2Extract, TicTacTree),
Segment};
false -> false ->
replace_segment(SegLeaf1Upd, SegLeaf2Upd, UpdatedTree
L1Extract, L2Extract, TicTacTree)
end. end.
-spec alter_segment(integer(), integer(), tictactree()) -> tictactree(). -spec alter_segment(integer(), integer(), tictactree()) -> tictactree().
@ -241,8 +259,9 @@ alter_segment(Segment, Hash, Tree) ->
%% Returns a list of segment IDs which hold differences between the state %% Returns a list of segment IDs which hold differences between the state
%% represented by the two trees. %% represented by the two trees.
find_dirtyleaves(SrcTree, SnkTree) -> find_dirtyleaves(SrcTree, SnkTree) ->
Size = SrcTree#tictactree.size, SizeSrc = SrcTree#tictactree.size,
Size = SnkTree#tictactree.size, SizeSnk = SnkTree#tictactree.size,
true = SizeSrc == SizeSnk,
IdxList = find_dirtysegments(fetch_root(SrcTree), fetch_root(SnkTree)), IdxList = find_dirtysegments(fetch_root(SrcTree), fetch_root(SnkTree)),
SrcLeaves = fetch_leaves(SrcTree, IdxList), SrcLeaves = fetch_leaves(SrcTree, IdxList),
@ -250,12 +269,19 @@ find_dirtyleaves(SrcTree, SnkTree) ->
FoldFun = FoldFun =
fun(Idx, Acc) -> fun(Idx, Acc) ->
{Idx, SrcLeaf} = lists:keyfind(Idx, 1, SrcLeaves), {_, SrcLeaf} = lists:keyfind(Idx, 1, SrcLeaves),
{Idx, SnkLeaf} = lists:keyfind(Idx, 1, SnkLeaves), {_, SnkLeaf} = lists:keyfind(Idx, 1, SnkLeaves),
L2IdxList = segmentcompare(SrcLeaf, SnkLeaf), L2IdxList = segmentcompare(SrcLeaf, SnkLeaf),
Acc ++ lists:map(fun(X) -> X + Idx * ?L2_CHUNKSIZE end, L2IdxList) lists:foldl(
fun(X, InnerAcc) ->
SegID = X + Idx * ?L2_CHUNKSIZE,
[SegID|InnerAcc]
end, end,
lists:sort(lists:foldl(FoldFun, [], IdxList)). Acc,
L2IdxList)
end,
%% Output not sorted, as sorted by the design of the construction process
lists:foldl(FoldFun, [], IdxList).
-spec find_dirtysegments(binary(), binary()) -> list(integer()). -spec find_dirtysegments(binary(), binary()) -> list(integer()).
%% @doc %% @doc
@ -268,7 +294,7 @@ find_dirtysegments(SrcBin, SinkBin) ->
%% @doc %% @doc
%% Return the level1 binary for a tree. %% Return the level1 binary for a tree.
fetch_root(TicTacTree) -> fetch_root(TicTacTree) ->
TicTacTree#tictactree.level1. from_level1_map(TicTacTree#tictactree.level1).
-spec fetch_leaves(tictactree(), list(integer())) -> list(). -spec fetch_leaves(tictactree(), list(integer())) -> list().
%% @doc %% @doc
@ -303,15 +329,21 @@ merge_trees(TreeA, TreeB) ->
NewLevel2 = merge_binaries(L2A, L2B), NewLevel2 = merge_binaries(L2A, L2B),
array:set(SQN, NewLevel2, MergeL2) array:set(SQN, NewLevel2, MergeL2)
end, end,
NewLevel2 = lists:foldl(MergeFun, NewLevel2 =
lists:foldl(
MergeFun,
MergedTree#tictactree.level2, MergedTree#tictactree.level2,
lists:seq(0, MergedTree#tictactree.width - 1)), lists:seq(0, MergedTree#tictactree.width - 1)
),
MergedTree#tictactree{level1 = NewLevel1, level2 = NewLevel2}. MergedTree#tictactree{
level1 = to_level1_map(NewLevel1),
level2 = NewLevel2
}.
-spec get_segment(integer(), -spec get_segment(
integer()|xxsmall|xsmall|small|medium|large|xlarge) -> integer(),
integer(). integer()|xxsmall|xsmall|small|medium|large|xlarge) -> integer().
%% @doc %% @doc
%% Return the segment ID for a Key. Can pass the tree size or the actual %% Return the segment ID for a Key. Can pass the tree size or the actual
%% segment count derived from the size %% segment count derived from the size
@ -339,8 +371,8 @@ tictac_hash(BinKey, Val) when is_binary(BinKey) ->
end, end,
{HashKeyToSeg, AltHashKey bxor HashVal}. {HashKeyToSeg, AltHashKey bxor HashVal}.
-spec keyto_doublesegment32(binary()) -spec keyto_doublesegment32(
-> {non_neg_integer(), non_neg_integer()}. binary()) -> {non_neg_integer(), non_neg_integer()}.
%% @doc %% @doc
%% Used in tictac_hash/2 to provide an alternative hash of the key to bxor with %% Used in tictac_hash/2 to provide an alternative hash of the key to bxor with
%% the value, as well as the segment hash to locate the leaf of the tree to be %% the value, as well as the segment hash to locate the leaf of the tree to be
@ -372,8 +404,8 @@ keyto_segment48(BinKey) ->
_Rest/binary>> = crypto:hash(md5, BinKey), _Rest/binary>> = crypto:hash(md5, BinKey),
{segment_hash, SegmentID, ExtraHash, AltHash}. {segment_hash, SegmentID, ExtraHash, AltHash}.
-spec generate_segmentfilter_list(list(integer()), tree_size()) -spec generate_segmentfilter_list(
-> false|list(integer()). list(integer()), tree_size()) -> false|list(integer()).
%% @doc %% @doc
%% Cannot accelerate segment listing for trees below certain sizes, so check %% Cannot accelerate segment listing for trees below certain sizes, so check
%% the creation of segment filter lists with this function %% the creation of segment filter lists with this function
@ -398,8 +430,8 @@ generate_segmentfilter_list(SegmentList, Size) ->
SegmentList SegmentList
end. end.
-spec adjust_segmentmatch_list(list(integer()), tree_size(), tree_size()) -spec adjust_segmentmatch_list(
-> list(integer()). list(integer()), tree_size(), tree_size()) -> list(integer()).
%% @doc %% @doc
%% If we have dirty segments discovered by comparing trees of size CompareSize, %% If we have dirty segments discovered by comparing trees of size CompareSize,
%% and we want to see if it matches a segment for a key which was created for a %% and we want to see if it matches a segment for a key which was created for a
@ -441,8 +473,8 @@ adjust_segmentmatch_list(SegmentList, CompareSize, StoreSize) ->
end. end.
-spec match_segment({integer(), tree_size()}, {integer(), tree_size()}) -spec match_segment(
-> boolean(). {integer(), tree_size()}, {integer(), tree_size()}) -> boolean().
%% @doc %% @doc
%% Does segment A match segment B - given that segment A was generated using %% Does segment A match segment B - given that segment A was generated using
%% Tree size A and segment B was generated using Tree Size B %% Tree size A and segment B was generated using Tree Size B
@ -462,7 +494,28 @@ join_segment(BranchID, LeafID) ->
%%% Internal functions %%% Internal functions
%%%============================================================================ %%%============================================================================
-spec extract_segment(integer(), tictactree()) -> -spec to_level1_map(binary()) -> level1_map().
to_level1_map(L1Bin) ->
to_level1_map_loop(L1Bin, maps:new(), 0).
to_level1_map_loop(<<>>, L1MapAcc, _Idx) ->
L1MapAcc;
to_level1_map_loop(<<Slice:64/binary, Rest/binary>>, L1MapAcc, Idx) ->
to_level1_map_loop(Rest, maps:put(Idx, Slice, L1MapAcc), Idx + 1).
-spec from_level1_map(level1_map()) -> binary().
from_level1_map(L1Map) ->
lists:foldl(
fun(I, Acc) ->
<<Acc/binary, (maps:get(I, L1Map))/binary>>
end,
<<>>,
lists:seq(0, maps:size(L1Map) - 1)
).
-spec extract_segment(
integer(), tictactree()) ->
{integer(), integer(), tree_extract(), tree_extract()}. {integer(), integer(), tree_extract(), tree_extract()}.
%% @doc %% @doc
%% Extract the Level 1 and Level 2 slices from a tree to prepare an update %% Extract the Level 1 and Level 2 slices from a tree to prepare an update
@ -472,9 +525,10 @@ extract_segment(Segment, TicTacTree) ->
Level1Pos = Level1Pos =
(Segment bsr ?L2_BITSIZE) (Segment bsr ?L2_BITSIZE)
band (TicTacTree#tictactree.width - 1), band (TicTacTree#tictactree.width - 1),
Level1Slice = Level1Pos div 16,
Level2BytePos = ?HASH_SIZE * Level2Pos, Level2BytePos = ?HASH_SIZE * Level2Pos,
Level1BytePos = ?HASH_SIZE * Level1Pos, Level1BytePos = ?HASH_SIZE * (Level1Pos rem 16),
Level2 = get_level2(TicTacTree, Level1Pos), Level2 = get_level2(TicTacTree, Level1Pos),
@ -484,7 +538,7 @@ extract_segment(Segment, TicTacTree) ->
PostL2/binary>> = Level2, PostL2/binary>> = Level2,
<<PreL1:Level1BytePos/binary, <<PreL1:Level1BytePos/binary,
SegLeaf1:HashIntLength/integer, SegLeaf1:HashIntLength/integer,
PostL1/binary>> = TicTacTree#tictactree.level1, PostL1/binary>> = maps:get(Level1Slice, TicTacTree#tictactree.level1),
{SegLeaf1, {SegLeaf1,
SegLeaf2, SegLeaf2,
@ -492,25 +546,26 @@ extract_segment(Segment, TicTacTree) ->
{PreL2, Level2BytePos, Level2Pos, HashIntLength, PostL2}}. {PreL2, Level2BytePos, Level2Pos, HashIntLength, PostL2}}.
-spec replace_segment(integer(), integer(), -spec replace_segment(
tree_extract(), tree_extract(), integer(), integer(), tree_extract(), tree_extract(), tictactree()) ->
tictactree()) -> tictactree(). tictactree().
%% @doc %% @doc
%% Replace a slice of a tree %% Replace a slice of a tree
replace_segment(L1Hash, L2Hash, L1Extract, L2Extract, TicTacTree) -> replace_segment(L1Hash, L2Hash, L1Extract, L2Extract, TicTacTree) ->
{PreL1, Level1BytePos, Level1Pos, HashIntLength, PostL1} = L1Extract, {PreL1, Level1BytePos, Level1Pos, HashIntLength, PostL1} = L1Extract,
{PreL2, Level2BytePos, _Level2Pos, HashIntLength, PostL2} = L2Extract, {PreL2, Level2BytePos, _Level2Pos, HashIntLength, PostL2} = L2Extract,
Level1Slice = Level1Pos div 16,
Level1Upd = <<PreL1:Level1BytePos/binary, Level1Upd = <<PreL1:Level1BytePos/binary,
L1Hash:HashIntLength/integer, L1Hash:HashIntLength/integer,
PostL1/binary>>, PostL1/binary>>,
Level2Upd = <<PreL2:Level2BytePos/binary, Level2Upd = <<PreL2:Level2BytePos/binary,
L2Hash:HashIntLength/integer, L2Hash:HashIntLength/integer,
PostL2/binary>>, PostL2/binary>>,
TicTacTree#tictactree{level1 = Level1Upd, TicTacTree#tictactree{
level2 = array:set(Level1Pos, level1 = maps:put(Level1Slice, Level1Upd, TicTacTree#tictactree.level1),
Level2Upd, level2 = array:set(Level1Pos, Level2Upd, TicTacTree#tictactree.level2)}.
TicTacTree#tictactree.level2)}.
get_level2(TicTacTree, L1Pos) -> get_level2(TicTacTree, L1Pos) ->
case array:get(L1Pos, TicTacTree#tictactree.level2) of case array:get(L1Pos, TicTacTree#tictactree.level2) of
@ -553,7 +608,7 @@ segmentcompare(SrcBin, SnkBin, Acc, Counter) ->
<<SrcHash:?HASH_SIZE/binary, SrcTail/binary>> = SrcBin, <<SrcHash:?HASH_SIZE/binary, SrcTail/binary>> = SrcBin,
<<SnkHash:?HASH_SIZE/binary, SnkTail/binary>> = SnkBin, <<SnkHash:?HASH_SIZE/binary, SnkTail/binary>> = SnkBin,
case SrcHash of case SrcHash of
SnkHash -> H when H == SnkHash ->
segmentcompare(SrcTail, SnkTail, Acc, Counter + 1); segmentcompare(SrcTail, SnkTail, Acc, Counter + 1);
_ -> _ ->
segmentcompare(SrcTail, SnkTail, [Counter|Acc], Counter + 1) segmentcompare(SrcTail, SnkTail, [Counter|Acc], Counter + 1)
@ -576,7 +631,7 @@ merge_binaries(BinA, BinB) ->
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
checktree(TicTacTree) -> checktree(TicTacTree) ->
checktree(TicTacTree#tictactree.level1, TicTacTree, 0). checktree(from_level1_map(TicTacTree#tictactree.level1), TicTacTree, 0).
checktree(<<>>, TicTacTree, Counter) -> checktree(<<>>, TicTacTree, Counter) ->
true = TicTacTree#tictactree.width == Counter; true = TicTacTree#tictactree.width == Counter;
@ -656,6 +711,7 @@ simple_test_withsize(Size) ->
DL0 = find_dirtyleaves(Tree1, Tree0), DL0 = find_dirtyleaves(Tree1, Tree0),
?assertMatch(true, lists:member(GetSegFun(K1), DL0)), ?assertMatch(true, lists:member(GetSegFun(K1), DL0)),
DL1 = find_dirtyleaves(Tree3, Tree1), DL1 = find_dirtyleaves(Tree3, Tree1),
?assertMatch(DL1, lists:sort(DL1)),
?assertMatch(true, lists:member(GetSegFun(K2), DL1)), ?assertMatch(true, lists:member(GetSegFun(K2), DL1)),
?assertMatch(true, lists:member(GetSegFun(K3), DL1)), ?assertMatch(true, lists:member(GetSegFun(K3), DL1)),
?assertMatch(false, lists:member(GetSegFun(K1), DL1)), ?assertMatch(false, lists:member(GetSegFun(K1), DL1)),
@ -665,6 +721,53 @@ simple_test_withsize(Size) ->
ImpTree3 = import_tree(ExpTree3), ImpTree3 = import_tree(ExpTree3),
?assertMatch(DL1, find_dirtyleaves(ImpTree3, Tree1)). ?assertMatch(DL1, find_dirtyleaves(ImpTree3, Tree1)).
dirtyleaves_sorted_test() ->
Tree0 = new_tree(test, large),
KVL1 =
lists:map(
fun(I) ->
{{o, to_bucket(I rem 8), to_key(I), null},
{is_hash, erlang:phash2(integer_to_binary(I))}}
end,
lists:seq(1, 50000)
),
KVL2 =
lists:map(
fun(I) ->
{{o, to_bucket(I rem 8), to_key(I), null},
{is_hash, erlang:phash2(integer_to_binary(I))}}
end,
lists:seq(100000, 150000)
),
Tree1 =
lists:foldl(
fun({K, V}, Acc) ->
add_kv(Acc, K, V, fun(K0, V0) -> {element(3, K0), V0} end)
end,
Tree0,
KVL1
),
Tree2 =
lists:foldl(
fun({K, V}, Acc) ->
add_kv(Acc, K, V, fun(K0, V0) -> {element(3, K0), V0} end)
end,
Tree0,
KVL2
),
SW0 = os:system_time(millisecond),
DL1 = find_dirtyleaves(Tree1, Tree2),
DL2 = find_dirtyleaves(Tree2, Tree1),
io:format(
user,
"Finding approx 100K dirty leaves twice in ~w milliseconds~n",
[os:system_time(millisecond) - SW0]
),
?assertMatch(DL1, lists:sort(DL1)),
?assertMatch(DL2, lists:sort(DL2)),
?assertMatch(DL1, DL2).
merge_bysize_small_test() -> merge_bysize_small_test() ->
merge_test_withsize(small). merge_test_withsize(small).
@ -870,6 +973,70 @@ find_dirtysegments_withanemptytree_test() ->
?assertMatch(ExpectedAnswer, find_dirtysegments(fetch_root(T3), <<>>)). ?assertMatch(ExpectedAnswer, find_dirtysegments(fetch_root(T3), <<>>)).
tictac_perf_test_() ->
{timeout, 120, fun tictac_perf_tester_multi/0}.
tictac_perf_tester_multi() ->
tictac_perf_tester(1000000, large),
tictac_perf_tester(40000, small).
tictac_perf_tester(KeyCount, TreeSize) ->
io:format(user, "Testing with Tree Size ~w~n", [TreeSize]),
io:format(user, "Generating ~w Keys and Hashes~n", [KeyCount]),
SW0 = os:system_time(millisecond),
KVL =
lists:map(
fun(I) ->
{{o, to_bucket(I rem 8), to_key(I), null},
{is_hash, erlang:phash2(integer_to_binary(I))}}
end,
lists:seq(1, KeyCount)
),
SW1 = os:system_time(millisecond),
io:format(user, "Generating Keys took ~w milliseconds~n", [SW1 - SW0]),
Tree = new_tree(test, TreeSize),
log_memory_footprint(),
SW2 = os:system_time(millisecond),
io:format(user, "Generating new tree took ~w milliseconds~n", [SW2 - SW1]),
UpdTree =
lists:foldl(
fun({K, V}, Acc) ->
add_kv(Acc, K, V, fun(K0, V0) -> {element(3, K0), V0} end)
end,
Tree,
KVL
),
SW3 = os:system_time(millisecond),
io:format(user, "Loading tree took ~w milliseconds~n", [SW3 - SW2]),
log_memory_footprint(),
ExportedTree = export_tree(UpdTree),
SW4 = os:system_time(millisecond),
io:format(user, "Exporting tree took ~w milliseconds~n", [SW4 - SW3]),
ImportedTree = import_tree(ExportedTree),
SW5 = os:system_time(millisecond),
io:format(user, "Importing tree took ~w milliseconds~n", [SW5 - SW4]),
log_memory_footprint(),
?assertMatch([], find_dirtyleaves(UpdTree, ImportedTree)).
to_key(N) ->
list_to_binary(io_lib:format("K~8..0B", [N])).
to_bucket(N) ->
list_to_binary(io_lib:format("B~8..0B", [N])).
log_memory_footprint() ->
io:format(user, "Memory footprint ~0p~n", [erlang:memory()]).
-endif. -endif.