Replace ledger-side gb_trees
Try to make minimal change to replace gb_trees with gb_tree API-like skiplists
This commit is contained in:
parent
c9afe34fea
commit
03d025d581
5 changed files with 490 additions and 525 deletions
|
@ -149,7 +149,7 @@
|
||||||
-record(state, {inker :: pid(),
|
-record(state, {inker :: pid(),
|
||||||
penciller :: pid(),
|
penciller :: pid(),
|
||||||
cache_size :: integer(),
|
cache_size :: integer(),
|
||||||
ledger_cache :: gb_trees:tree(),
|
ledger_cache :: list(), % a skiplist
|
||||||
is_snapshot :: boolean(),
|
is_snapshot :: boolean(),
|
||||||
slow_offer = false :: boolean()}).
|
slow_offer = false :: boolean()}).
|
||||||
|
|
||||||
|
@ -233,14 +233,14 @@ init([Opts]) ->
|
||||||
{ok, #state{inker=Inker,
|
{ok, #state{inker=Inker,
|
||||||
penciller=Penciller,
|
penciller=Penciller,
|
||||||
cache_size=CacheSize,
|
cache_size=CacheSize,
|
||||||
ledger_cache=gb_trees:empty(),
|
ledger_cache=leveled_skiplist:empty(),
|
||||||
is_snapshot=false}};
|
is_snapshot=false}};
|
||||||
Bookie ->
|
Bookie ->
|
||||||
{ok,
|
{ok,
|
||||||
{Penciller, LedgerCache},
|
{Penciller, LedgerCache},
|
||||||
Inker} = book_snapshotstore(Bookie, self(), ?SNAPSHOT_TIMEOUT),
|
Inker} = book_snapshotstore(Bookie, self(), ?SNAPSHOT_TIMEOUT),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(Penciller,
|
ok = leveled_penciller:pcl_loadsnapshot(Penciller,
|
||||||
gb_trees:empty()),
|
leveled_skiplist:empty()),
|
||||||
leveled_log:log("B0002", [Inker, Penciller]),
|
leveled_log:log("B0002", [Inker, Penciller]),
|
||||||
{ok, #state{penciller=Penciller,
|
{ok, #state{penciller=Penciller,
|
||||||
inker=Inker,
|
inker=Inker,
|
||||||
|
@ -431,7 +431,7 @@ bucket_stats(State, Bucket, Tag) ->
|
||||||
{LedgerSnapshot, LedgerCache},
|
{LedgerSnapshot, LedgerCache},
|
||||||
_JournalSnapshot} = snapshot_store(State, ledger),
|
_JournalSnapshot} = snapshot_store(State, ledger),
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
StartKey = leveled_codec:to_ledgerkey(Bucket, null, Tag),
|
StartKey = leveled_codec:to_ledgerkey(Bucket, null, Tag),
|
||||||
|
@ -454,7 +454,7 @@ binary_bucketlist(State, Tag, {FoldBucketsFun, InitAcc}) ->
|
||||||
{LedgerSnapshot, LedgerCache},
|
{LedgerSnapshot, LedgerCache},
|
||||||
_JournalSnapshot} = snapshot_store(State, ledger),
|
_JournalSnapshot} = snapshot_store(State, ledger),
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
BucketAcc = get_nextbucket(null,
|
BucketAcc = get_nextbucket(null,
|
||||||
|
@ -509,7 +509,7 @@ index_query(State,
|
||||||
{B, null}
|
{B, null}
|
||||||
end,
|
end,
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
StartKey = leveled_codec:to_ledgerkey(Bucket,
|
StartKey = leveled_codec:to_ledgerkey(Bucket,
|
||||||
|
@ -551,7 +551,7 @@ hashtree_query(State, Tag, JournalCheck) ->
|
||||||
{LedgerSnapshot, LedgerCache},
|
{LedgerSnapshot, LedgerCache},
|
||||||
JournalSnapshot} = snapshot_store(State, SnapType),
|
JournalSnapshot} = snapshot_store(State, SnapType),
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
StartKey = leveled_codec:to_ledgerkey(null, null, Tag),
|
StartKey = leveled_codec:to_ledgerkey(null, null, Tag),
|
||||||
|
@ -602,7 +602,7 @@ foldobjects(State, Tag, StartKey, EndKey, FoldObjectsFun) ->
|
||||||
{FoldObjectsFun, []}
|
{FoldObjectsFun, []}
|
||||||
end,
|
end,
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
AccFun = accumulate_objects(FoldFun, JournalSnapshot, Tag),
|
AccFun = accumulate_objects(FoldFun, JournalSnapshot, Tag),
|
||||||
|
@ -623,7 +623,7 @@ bucketkey_query(State, Tag, Bucket, {FoldKeysFun, InitAcc}) ->
|
||||||
{LedgerSnapshot, LedgerCache},
|
{LedgerSnapshot, LedgerCache},
|
||||||
_JournalSnapshot} = snapshot_store(State, ledger),
|
_JournalSnapshot} = snapshot_store(State, ledger),
|
||||||
Folder = fun() ->
|
Folder = fun() ->
|
||||||
leveled_log:log("B0004", [gb_trees:size(LedgerCache)]),
|
leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]),
|
||||||
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
LedgerCache),
|
LedgerCache),
|
||||||
SK = leveled_codec:to_ledgerkey(Bucket, null, Tag),
|
SK = leveled_codec:to_ledgerkey(Bucket, null, Tag),
|
||||||
|
@ -697,7 +697,7 @@ startup(InkerOpts, PencillerOpts) ->
|
||||||
|
|
||||||
|
|
||||||
fetch_head(Key, Penciller, LedgerCache) ->
|
fetch_head(Key, Penciller, LedgerCache) ->
|
||||||
case gb_trees:lookup(Key, LedgerCache) of
|
case leveled_skiplist:lookup(Key, LedgerCache) of
|
||||||
{value, Head} ->
|
{value, Head} ->
|
||||||
Head;
|
Head;
|
||||||
none ->
|
none ->
|
||||||
|
@ -863,18 +863,18 @@ preparefor_ledgercache(_Type, LedgerKey, SQN, Obj, Size, {IndexSpecs, TTL}) ->
|
||||||
|
|
||||||
|
|
||||||
addto_ledgercache(Changes, Cache) ->
|
addto_ledgercache(Changes, Cache) ->
|
||||||
lists:foldl(fun({K, V}, Acc) -> gb_trees:enter(K, V, Acc) end,
|
lists:foldl(fun({K, V}, Acc) -> leveled_skiplist:enter(K, V, Acc) end,
|
||||||
Cache,
|
Cache,
|
||||||
Changes).
|
Changes).
|
||||||
|
|
||||||
maybepush_ledgercache(MaxCacheSize, Cache, Penciller) ->
|
maybepush_ledgercache(MaxCacheSize, Cache, Penciller) ->
|
||||||
CacheSize = gb_trees:size(Cache),
|
CacheSize = leveled_skiplist:size(Cache),
|
||||||
TimeToPush = maybe_withjitter(CacheSize, MaxCacheSize),
|
TimeToPush = maybe_withjitter(CacheSize, MaxCacheSize),
|
||||||
if
|
if
|
||||||
TimeToPush ->
|
TimeToPush ->
|
||||||
case leveled_penciller:pcl_pushmem(Penciller, Cache) of
|
case leveled_penciller:pcl_pushmem(Penciller, Cache) of
|
||||||
ok ->
|
ok ->
|
||||||
{ok, gb_trees:empty()};
|
{ok, leveled_skiplist:empty()};
|
||||||
returned ->
|
returned ->
|
||||||
{returned, Cache}
|
{returned, Cache}
|
||||||
end;
|
end;
|
||||||
|
|
|
@ -633,7 +633,7 @@ load_from_sequence(MinSQN, FilterFun, Penciller, [{_LowSQN, FN, Pid}|Rest]) ->
|
||||||
load_between_sequence(MinSQN, MaxSQN, FilterFun, Penciller,
|
load_between_sequence(MinSQN, MaxSQN, FilterFun, Penciller,
|
||||||
CDBpid, StartPos, FN, Rest) ->
|
CDBpid, StartPos, FN, Rest) ->
|
||||||
leveled_log:log("I0014", [FN, MinSQN]),
|
leveled_log:log("I0014", [FN, MinSQN]),
|
||||||
InitAcc = {MinSQN, MaxSQN, gb_trees:empty()},
|
InitAcc = {MinSQN, MaxSQN, leveled_skiplist:empty()},
|
||||||
Res = case leveled_cdb:cdb_scan(CDBpid, FilterFun, InitAcc, StartPos) of
|
Res = case leveled_cdb:cdb_scan(CDBpid, FilterFun, InitAcc, StartPos) of
|
||||||
{eof, {AccMinSQN, _AccMaxSQN, AccKL}} ->
|
{eof, {AccMinSQN, _AccMaxSQN, AccKL}} ->
|
||||||
ok = push_to_penciller(Penciller, AccKL),
|
ok = push_to_penciller(Penciller, AccKL),
|
||||||
|
|
|
@ -212,7 +212,7 @@
|
||||||
|
|
||||||
levelzero_pending = false :: boolean(),
|
levelzero_pending = false :: boolean(),
|
||||||
levelzero_constructor :: pid(),
|
levelzero_constructor :: pid(),
|
||||||
levelzero_cache = [] :: list(), % a list of gb_trees
|
levelzero_cache = [] :: list(), % a list of skiplists
|
||||||
levelzero_index :: array:array(),
|
levelzero_index :: array:array(),
|
||||||
levelzero_size = 0 :: integer(),
|
levelzero_size = 0 :: integer(),
|
||||||
levelzero_maxcachesize :: integer(),
|
levelzero_maxcachesize :: integer(),
|
||||||
|
@ -220,7 +220,7 @@
|
||||||
is_snapshot = false :: boolean(),
|
is_snapshot = false :: boolean(),
|
||||||
snapshot_fully_loaded = false :: boolean(),
|
snapshot_fully_loaded = false :: boolean(),
|
||||||
source_penciller :: pid(),
|
source_penciller :: pid(),
|
||||||
levelzero_astree :: gb_trees:tree(),
|
levelzero_astree :: list(), % skiplist
|
||||||
|
|
||||||
ongoing_work = [] :: list(),
|
ongoing_work = [] :: list(),
|
||||||
work_backlog = false :: boolean()}).
|
work_backlog = false :: boolean()}).
|
||||||
|
@ -366,25 +366,24 @@ handle_call({fetch_keys, StartKey, EndKey, AccFun, InitAcc, MaxKeys},
|
||||||
_From,
|
_From,
|
||||||
State=#state{snapshot_fully_loaded=Ready})
|
State=#state{snapshot_fully_loaded=Ready})
|
||||||
when Ready == true ->
|
when Ready == true ->
|
||||||
L0AsTree =
|
L0AsList =
|
||||||
case State#state.levelzero_astree of
|
case State#state.levelzero_astree of
|
||||||
undefined ->
|
undefined ->
|
||||||
leveled_pmem:merge_trees(StartKey,
|
leveled_pmem:merge_trees(StartKey,
|
||||||
EndKey,
|
EndKey,
|
||||||
State#state.levelzero_cache,
|
State#state.levelzero_cache,
|
||||||
gb_trees:empty());
|
leveled_skiplist:empty());
|
||||||
Tree ->
|
List ->
|
||||||
Tree
|
List
|
||||||
end,
|
end,
|
||||||
L0iter = gb_trees:iterator(L0AsTree),
|
|
||||||
SFTiter = initiate_rangequery_frommanifest(StartKey,
|
SFTiter = initiate_rangequery_frommanifest(StartKey,
|
||||||
EndKey,
|
EndKey,
|
||||||
State#state.manifest),
|
State#state.manifest),
|
||||||
Acc = keyfolder({L0iter, SFTiter},
|
Acc = keyfolder({L0AsList, SFTiter},
|
||||||
{StartKey, EndKey},
|
{StartKey, EndKey},
|
||||||
{AccFun, InitAcc},
|
{AccFun, InitAcc},
|
||||||
MaxKeys),
|
MaxKeys),
|
||||||
{reply, Acc, State#state{levelzero_astree = L0AsTree}};
|
{reply, Acc, State#state{levelzero_astree = L0AsList}};
|
||||||
handle_call(work_for_clerk, From, State) ->
|
handle_call(work_for_clerk, From, State) ->
|
||||||
{UpdState, Work} = return_work(State, From),
|
{UpdState, Work} = return_work(State, From),
|
||||||
{reply, Work, UpdState};
|
{reply, Work, UpdState};
|
||||||
|
@ -985,77 +984,73 @@ keyfolder(IMMiter, SFTiter, StartKey, EndKey, {AccFun, Acc}) ->
|
||||||
|
|
||||||
keyfolder(_Iterators, _KeyRange, {_AccFun, Acc}, MaxKeys) when MaxKeys == 0 ->
|
keyfolder(_Iterators, _KeyRange, {_AccFun, Acc}, MaxKeys) when MaxKeys == 0 ->
|
||||||
Acc;
|
Acc;
|
||||||
keyfolder({null, SFTiter}, KeyRange, {AccFun, Acc}, MaxKeys) ->
|
keyfolder({[], SFTiter}, KeyRange, {AccFun, Acc}, MaxKeys) ->
|
||||||
{StartKey, EndKey} = KeyRange,
|
{StartKey, EndKey} = KeyRange,
|
||||||
case find_nextkey(SFTiter, StartKey, EndKey) of
|
case find_nextkey(SFTiter, StartKey, EndKey) of
|
||||||
no_more_keys ->
|
no_more_keys ->
|
||||||
Acc;
|
Acc;
|
||||||
{NxSFTiter, {SFTKey, SFTVal}} ->
|
{NxSFTiter, {SFTKey, SFTVal}} ->
|
||||||
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
||||||
keyfolder({null, NxSFTiter}, KeyRange, {AccFun, Acc1}, MaxKeys - 1)
|
keyfolder({[], NxSFTiter}, KeyRange, {AccFun, Acc1}, MaxKeys - 1)
|
||||||
end;
|
end;
|
||||||
keyfolder({IMMiterator, SFTiterator}, KeyRange, {AccFun, Acc}, MaxKeys) ->
|
keyfolder({[{IMMKey, IMMVal}|NxIMMiterator], SFTiterator}, KeyRange,
|
||||||
|
{AccFun, Acc}, MaxKeys) ->
|
||||||
{StartKey, EndKey} = KeyRange,
|
{StartKey, EndKey} = KeyRange,
|
||||||
case gb_trees:next(IMMiterator) of
|
case {IMMKey < StartKey, leveled_codec:endkey_passed(EndKey, IMMKey)} of
|
||||||
none ->
|
{true, _} ->
|
||||||
% There are no more keys in the in-memory iterator, so now
|
|
||||||
% iterate only over the remaining keys in the SFT iterator
|
|
||||||
keyfolder({null, SFTiterator}, KeyRange, {AccFun, Acc}, MaxKeys);
|
|
||||||
{IMMKey, _IMMVal, NxIMMiterator} when IMMKey < StartKey ->
|
|
||||||
% Normally everything is pre-filterd, but the IMM iterator can
|
% Normally everything is pre-filterd, but the IMM iterator can
|
||||||
% be re-used and do may be behind the StartKey if the StartKey has
|
% be re-used and so may be behind the StartKey if the StartKey has
|
||||||
% advanced from the previous use
|
% advanced from the previous use
|
||||||
keyfolder({NxIMMiterator, SFTiterator},
|
keyfolder({NxIMMiterator, SFTiterator},
|
||||||
KeyRange,
|
KeyRange,
|
||||||
{AccFun, Acc},
|
{AccFun, Acc},
|
||||||
MaxKeys);
|
MaxKeys);
|
||||||
{IMMKey, IMMVal, NxIMMiterator} ->
|
{false, true} ->
|
||||||
case leveled_codec:endkey_passed(EndKey, IMMKey) of
|
% There are no more keys in-range in the in-memory
|
||||||
true ->
|
% iterator, so take action as if this iterator is empty
|
||||||
% There are no more keys in-range in the in-memory
|
% (see above)
|
||||||
% iterator, so take action as if this iterator is empty
|
keyfolder({[], SFTiterator},
|
||||||
% (see above)
|
KeyRange,
|
||||||
keyfolder({null, SFTiterator},
|
{AccFun, Acc},
|
||||||
|
MaxKeys);
|
||||||
|
{false, false} ->
|
||||||
|
case find_nextkey(SFTiterator, StartKey, EndKey) of
|
||||||
|
no_more_keys ->
|
||||||
|
% No more keys in range in the persisted store, so use the
|
||||||
|
% in-memory KV as the next
|
||||||
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
|
keyfolder({NxIMMiterator, SFTiterator},
|
||||||
KeyRange,
|
KeyRange,
|
||||||
{AccFun, Acc},
|
{AccFun, Acc1},
|
||||||
MaxKeys);
|
MaxKeys - 1);
|
||||||
false ->
|
{NxSFTiterator, {SFTKey, SFTVal}} ->
|
||||||
case find_nextkey(SFTiterator, StartKey, EndKey) of
|
% There is a next key, so need to know which is the
|
||||||
no_more_keys ->
|
% next key between the two (and handle two keys
|
||||||
% No more keys in range in the persisted store, so use the
|
% with different sequence numbers).
|
||||||
% in-memory KV as the next
|
case leveled_codec:key_dominates({IMMKey,
|
||||||
|
IMMVal},
|
||||||
|
{SFTKey,
|
||||||
|
SFTVal}) of
|
||||||
|
left_hand_first ->
|
||||||
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
keyfolder({NxIMMiterator, SFTiterator},
|
keyfolder({NxIMMiterator, SFTiterator},
|
||||||
KeyRange,
|
KeyRange,
|
||||||
{AccFun, Acc1},
|
{AccFun, Acc1},
|
||||||
MaxKeys - 1);
|
MaxKeys - 1);
|
||||||
{NxSFTiterator, {SFTKey, SFTVal}} ->
|
right_hand_first ->
|
||||||
% There is a next key, so need to know which is the
|
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
||||||
% next key between the two (and handle two keys
|
keyfolder({[{IMMKey, IMMVal}|NxIMMiterator],
|
||||||
% with different sequence numbers).
|
NxSFTiterator},
|
||||||
case leveled_codec:key_dominates({IMMKey,
|
KeyRange,
|
||||||
IMMVal},
|
{AccFun, Acc1},
|
||||||
{SFTKey,
|
MaxKeys - 1);
|
||||||
SFTVal}) of
|
left_hand_dominant ->
|
||||||
left_hand_first ->
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
keyfolder({NxIMMiterator, NxSFTiterator},
|
||||||
keyfolder({NxIMMiterator, SFTiterator},
|
KeyRange,
|
||||||
KeyRange,
|
{AccFun, Acc1},
|
||||||
{AccFun, Acc1},
|
MaxKeys - 1)
|
||||||
MaxKeys - 1);
|
|
||||||
right_hand_first ->
|
|
||||||
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
|
||||||
keyfolder({IMMiterator, NxSFTiterator},
|
|
||||||
KeyRange,
|
|
||||||
{AccFun, Acc1},
|
|
||||||
MaxKeys - 1);
|
|
||||||
left_hand_dominant ->
|
|
||||||
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
|
||||||
keyfolder({NxIMMiterator, NxSFTiterator},
|
|
||||||
KeyRange,
|
|
||||||
{AccFun, Acc1},
|
|
||||||
MaxKeys - 1)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
@ -1267,8 +1262,8 @@ confirm_delete_test() ->
|
||||||
|
|
||||||
|
|
||||||
maybe_pause_push(PCL, KL) ->
|
maybe_pause_push(PCL, KL) ->
|
||||||
T0 = gb_trees:empty(),
|
T0 = leveled_skiplist:empty(),
|
||||||
T1 = lists:foldl(fun({K, V}, Acc) -> gb_trees:enter(K, V, Acc) end,
|
T1 = lists:foldl(fun({K, V}, Acc) -> leveled_skiplist:enter(K, V, Acc) end,
|
||||||
T0,
|
T0,
|
||||||
KL),
|
KL),
|
||||||
case pcl_pushmem(PCL, T1) of
|
case pcl_pushmem(PCL, T1) of
|
||||||
|
@ -1335,7 +1330,7 @@ simple_server_test() ->
|
||||||
SnapOpts = #penciller_options{start_snapshot = true,
|
SnapOpts = #penciller_options{start_snapshot = true,
|
||||||
source_penciller = PCLr},
|
source_penciller = PCLr},
|
||||||
{ok, PclSnap} = pcl_start(SnapOpts),
|
{ok, PclSnap} = pcl_start(SnapOpts),
|
||||||
ok = pcl_loadsnapshot(PclSnap, gb_trees:empty()),
|
ok = pcl_loadsnapshot(PclSnap, leveled_skiplist:empty()),
|
||||||
?assertMatch(Key1, pcl_fetch(PclSnap, {o,"Bucket0001", "Key0001", null})),
|
?assertMatch(Key1, pcl_fetch(PclSnap, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PclSnap, {o,"Bucket0002", "Key0002", null})),
|
?assertMatch(Key2, pcl_fetch(PclSnap, {o,"Bucket0002", "Key0002", null})),
|
||||||
?assertMatch(Key3, pcl_fetch(PclSnap, {o,"Bucket0003", "Key0003", null})),
|
?assertMatch(Key3, pcl_fetch(PclSnap, {o,"Bucket0003", "Key0003", null})),
|
||||||
|
@ -1384,7 +1379,7 @@ simple_server_test() ->
|
||||||
term_to_binary("Hello")),
|
term_to_binary("Hello")),
|
||||||
|
|
||||||
{ok, PclSnap2} = pcl_start(SnapOpts),
|
{ok, PclSnap2} = pcl_start(SnapOpts),
|
||||||
ok = pcl_loadsnapshot(PclSnap2, gb_trees:empty()),
|
ok = pcl_loadsnapshot(PclSnap2, leveled_skiplist:empty()),
|
||||||
?assertMatch(false, pcl_checksequencenumber(PclSnap2,
|
?assertMatch(false, pcl_checksequencenumber(PclSnap2,
|
||||||
{o,
|
{o,
|
||||||
"Bucket0001",
|
"Bucket0001",
|
||||||
|
@ -1543,16 +1538,16 @@ foldwithimm_simple_test() ->
|
||||||
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
||||||
{5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]}
|
{5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]}
|
||||||
],
|
],
|
||||||
IMM0 = gb_trees:enter({o, "Bucket1", "Key6"},
|
IMM0 = leveled_skiplist:enter({o, "Bucket1", "Key6"},
|
||||||
{7, {active, infinity}, null},
|
{7, {active, infinity}, null},
|
||||||
gb_trees:empty()),
|
leveled_skiplist:empty()),
|
||||||
IMM1 = gb_trees:enter({o, "Bucket1", "Key1"},
|
IMM1 = leveled_skiplist:enter({o, "Bucket1", "Key1"},
|
||||||
{8, {active, infinity}, null},
|
{8, {active, infinity}, null},
|
||||||
IMM0),
|
IMM0),
|
||||||
IMM2 = gb_trees:enter({o, "Bucket1", "Key8"},
|
IMM2 = leveled_skiplist:enter({o, "Bucket1", "Key8"},
|
||||||
{9, {active, infinity}, null},
|
{9, {active, infinity}, null},
|
||||||
IMM1),
|
IMM1),
|
||||||
IMMiter = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM2),
|
IMMiter = leveled_skiplist:to_range(IMM2, {o, "Bucket1", "Key1"}),
|
||||||
AccFun = fun(K, V, Acc) -> SQN = leveled_codec:strip_to_seqonly({K, V}),
|
AccFun = fun(K, V, Acc) -> SQN = leveled_codec:strip_to_seqonly({K, V}),
|
||||||
Acc ++ [{K, SQN}] end,
|
Acc ++ [{K, SQN}] end,
|
||||||
Acc = keyfolder(IMMiter,
|
Acc = keyfolder(IMMiter,
|
||||||
|
@ -1564,10 +1559,10 @@ foldwithimm_simple_test() ->
|
||||||
{{o, "Bucket1", "Key5"}, 2},
|
{{o, "Bucket1", "Key5"}, 2},
|
||||||
{{o, "Bucket1", "Key6"}, 7}], Acc),
|
{{o, "Bucket1", "Key6"}, 7}], Acc),
|
||||||
|
|
||||||
IMM1A = gb_trees:enter({o, "Bucket1", "Key1"},
|
IMM1A = leveled_skiplist:enter({o, "Bucket1", "Key1"},
|
||||||
{8, {active, infinity}, null},
|
{8, {active, infinity}, null},
|
||||||
gb_trees:empty()),
|
leveled_skiplist:empty()),
|
||||||
IMMiterA = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM1A),
|
IMMiterA = leveled_skiplist:to_range(IMM1A, {o, "Bucket1", "Key1"}),
|
||||||
AccA = keyfolder(IMMiterA,
|
AccA = keyfolder(IMMiterA,
|
||||||
QueryArray,
|
QueryArray,
|
||||||
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
||||||
|
@ -1576,10 +1571,10 @@ foldwithimm_simple_test() ->
|
||||||
{{o, "Bucket1", "Key3"}, 3},
|
{{o, "Bucket1", "Key3"}, 3},
|
||||||
{{o, "Bucket1", "Key5"}, 2}], AccA),
|
{{o, "Bucket1", "Key5"}, 2}], AccA),
|
||||||
|
|
||||||
IMM3 = gb_trees:enter({o, "Bucket1", "Key4"},
|
IMM3 = leveled_skiplist:enter({o, "Bucket1", "Key4"},
|
||||||
{10, {active, infinity}, null},
|
{10, {active, infinity}, null},
|
||||||
IMM2),
|
IMM2),
|
||||||
IMMiterB = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM3),
|
IMMiterB = leveled_skiplist:to_range(IMM3, {o, "Bucket1", "Key1"}),
|
||||||
AccB = keyfolder(IMMiterB,
|
AccB = keyfolder(IMMiterB,
|
||||||
QueryArray,
|
QueryArray,
|
||||||
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
||||||
|
@ -1594,7 +1589,7 @@ create_file_test() ->
|
||||||
Filename = "../test/new_file.sft",
|
Filename = "../test/new_file.sft",
|
||||||
ok = file:write_file(Filename, term_to_binary("hello")),
|
ok = file:write_file(Filename, term_to_binary("hello")),
|
||||||
KVL = lists:usort(leveled_sft:generate_randomkeys(10000)),
|
KVL = lists:usort(leveled_sft:generate_randomkeys(10000)),
|
||||||
Tree = gb_trees:from_orddict(KVL),
|
Tree = leveled_skiplist:from_list(KVL),
|
||||||
FetchFun = fun(Slot) -> lists:nth(Slot, [Tree]) end,
|
FetchFun = fun(Slot) -> lists:nth(Slot, [Tree]) end,
|
||||||
{ok,
|
{ok,
|
||||||
SP,
|
SP,
|
||||||
|
|
|
@ -51,20 +51,13 @@
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-define(SLOT_WIDTH, {2048, 11}).
|
-define(SLOT_WIDTH, {4096, 12}).
|
||||||
-define(SKIP_WIDTH, 32).
|
|
||||||
-define(INFINITE_KEY, {null, null, null, null, null}).
|
|
||||||
-define(EMPTY_SKIPLIST, [{?INFINITE_KEY, []}]).
|
|
||||||
|
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
%%% API
|
%%% API
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
add_to_index(L0Index, L0Size, LevelMinus1, LedgerSQN, TreeList) ->
|
add_to_index(L0Index, L0Size, LevelMinus1, LedgerSQN, TreeList) ->
|
||||||
SW = os:timestamp(),
|
SW = os:timestamp(),
|
||||||
SlotInTreeList = length(TreeList) + 1,
|
SlotInTreeList = length(TreeList) + 1,
|
||||||
|
@ -83,7 +76,7 @@ add_to_index(L0Index, L0Size, LevelMinus1, LedgerSQN, TreeList) ->
|
||||||
Count0,
|
Count0,
|
||||||
array:set(Slot, [{Hash, SlotInTreeList}|L], HashIndex)}
|
array:set(Slot, [{Hash, SlotInTreeList}|L], HashIndex)}
|
||||||
end,
|
end,
|
||||||
LM1List = gb_trees:to_list(LevelMinus1),
|
LM1List = leveled_skiplist:to_list(LevelMinus1),
|
||||||
StartingT = {infinity, 0, L0Size, L0Index},
|
StartingT = {infinity, 0, L0Size, L0Index},
|
||||||
{MinSQN, MaxSQN, NewL0Size, UpdL0Index} = lists:foldl(FoldFun,
|
{MinSQN, MaxSQN, NewL0Size, UpdL0Index} = lists:foldl(FoldFun,
|
||||||
StartingT,
|
StartingT,
|
||||||
|
@ -103,7 +96,7 @@ to_list(Slots, FetchFun) ->
|
||||||
SlotList = lists:reverse(lists:seq(1, Slots)),
|
SlotList = lists:reverse(lists:seq(1, Slots)),
|
||||||
FullList = lists:foldl(fun(Slot, Acc) ->
|
FullList = lists:foldl(fun(Slot, Acc) ->
|
||||||
Tree = FetchFun(Slot),
|
Tree = FetchFun(Slot),
|
||||||
L = gb_trees:to_list(Tree),
|
L = leveled_skiplist:to_list(Tree),
|
||||||
lists:ukeymerge(1, Acc, L)
|
lists:ukeymerge(1, Acc, L)
|
||||||
end,
|
end,
|
||||||
[],
|
[],
|
||||||
|
@ -135,7 +128,7 @@ check_levelzero(Key, L0Index, TreeList) ->
|
||||||
{Found, KV};
|
{Found, KV};
|
||||||
false ->
|
false ->
|
||||||
CheckTree = lists:nth(SlotToCheck, TreeList),
|
CheckTree = lists:nth(SlotToCheck, TreeList),
|
||||||
case gb_trees:lookup(Key, CheckTree) of
|
case leveled_skiplist:lookup(Key, CheckTree) of
|
||||||
none ->
|
none ->
|
||||||
{Found, KV};
|
{Found, KV};
|
||||||
{value, Value} ->
|
{value, Value} ->
|
||||||
|
@ -146,170 +139,15 @@ check_levelzero(Key, L0Index, TreeList) ->
|
||||||
{false, not_found},
|
{false, not_found},
|
||||||
lists:reverse(lists:usort(SlotList))).
|
lists:reverse(lists:usort(SlotList))).
|
||||||
|
|
||||||
|
|
||||||
merge_trees(StartKey, EndKey, TreeList, LevelMinus1) ->
|
|
||||||
lists:foldl(fun(Tree, TreeAcc) ->
|
|
||||||
merge_nexttree(Tree, TreeAcc, StartKey, EndKey) end,
|
|
||||||
gb_trees:empty(),
|
|
||||||
lists:append(TreeList, [LevelMinus1])).
|
|
||||||
|
|
||||||
%%%============================================================================
|
|
||||||
%%% SkipList
|
|
||||||
%%%============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
addhash_to_index(HashIndex, Hash, Slot, Count) ->
|
|
||||||
L = array:get(Slot, HashIndex),
|
|
||||||
case lists:member(Hash, L) of
|
|
||||||
true ->
|
|
||||||
{HashIndex, Count};
|
|
||||||
false ->
|
|
||||||
{array:set(Slot, [Hash|L], HashIndex), Count + 1}
|
|
||||||
end.
|
|
||||||
|
|
||||||
merge_indexes(HashIndex, MergedIndex, Count, L0Slot) ->
|
|
||||||
lists:foldl(fun(Slot, {MHI, AccCount}) ->
|
|
||||||
HashList = array:get(Slot, HashIndex),
|
|
||||||
case length(HashList) > 0 of
|
|
||||||
true ->
|
|
||||||
merge_indexes_singleslot(HashList,
|
|
||||||
Slot,
|
|
||||||
MHI,
|
|
||||||
L0Slot,
|
|
||||||
AccCount);
|
|
||||||
false ->
|
|
||||||
{MHI, AccCount}
|
|
||||||
end end,
|
|
||||||
{MergedIndex, Count},
|
|
||||||
lists:seq(0, element(1, ?SLOT_WIDTH) - 1)).
|
|
||||||
|
|
||||||
merge_indexes_singleslot(HashList, IndexSlot, MergedIndex, L0Slot, Count) ->
|
|
||||||
L = array:get(IndexSlot, MergedIndex),
|
|
||||||
{UpdHL, UpdCount} = lists:foldl(fun(H, {HL, C}) ->
|
|
||||||
case lists:keymember(H, 1, L) of
|
|
||||||
true ->
|
|
||||||
{[{H, L0Slot}|HL], C + 1};
|
|
||||||
false ->
|
|
||||||
{[{H, L0Slot}|HL], C}
|
|
||||||
end end,
|
|
||||||
{L, Count},
|
|
||||||
HashList),
|
|
||||||
{array:set(IndexSlot, UpdHL, MergedIndex), UpdCount}.
|
|
||||||
|
|
||||||
skiplist_put(SkipList, Key, Value, Hash) ->
|
|
||||||
{MarkerKey, SubList} = lists:foldl(fun({Marker, SL}, Acc) ->
|
|
||||||
case Acc of
|
|
||||||
false ->
|
|
||||||
case Marker >= Key of
|
|
||||||
true ->
|
|
||||||
{Marker, SL};
|
|
||||||
false ->
|
|
||||||
Acc
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
Acc
|
|
||||||
end end,
|
|
||||||
false,
|
|
||||||
SkipList),
|
|
||||||
case Hash rem ?SKIP_WIDTH of
|
|
||||||
0 ->
|
|
||||||
{LHS, RHS} = lists:splitwith(fun({K, _V}) -> K < Key end, SubList),
|
|
||||||
SkpL1 = lists:keyreplace(MarkerKey, 1, SkipList, {MarkerKey, RHS}),
|
|
||||||
SkpL2 = [{Key, lists:ukeysort(1, [{Key, Value}|LHS])}|SkpL1],
|
|
||||||
lists:ukeysort(1, SkpL2);
|
|
||||||
_ ->
|
|
||||||
UpdSubList = lists:ukeysort(1, [{Key, Value}|SubList]),
|
|
||||||
lists:keyreplace(MarkerKey, 1, SkipList, {MarkerKey, UpdSubList})
|
|
||||||
end.
|
|
||||||
|
|
||||||
skiplist_generate(UnsortedKVL) ->
|
|
||||||
KVL = lists:ukeysort(1, UnsortedKVL),
|
|
||||||
Slots = length(KVL) div ?SKIP_WIDTH,
|
|
||||||
SkipList0 = lists:map(fun(X) ->
|
|
||||||
N = X * ?SKIP_WIDTH,
|
|
||||||
{K, _V} = lists:nth(N, KVL),
|
|
||||||
{K, lists:sublist(KVL,
|
|
||||||
N - ?SKIP_WIDTH + 1,
|
|
||||||
?SKIP_WIDTH)}
|
|
||||||
end,
|
|
||||||
lists:seq(1, length(KVL) div ?SKIP_WIDTH)),
|
|
||||||
case Slots * ?SKIP_WIDTH < length(KVL) of
|
|
||||||
true ->
|
|
||||||
{LastK, _V} = lists:last(KVL),
|
|
||||||
SkipList0 ++ [{LastK, lists:nthtail(Slots * ?SKIP_WIDTH, KVL)}];
|
|
||||||
false ->
|
|
||||||
SkipList0
|
|
||||||
end.
|
|
||||||
|
|
||||||
skiplist_get(SkipList, Key) ->
|
|
||||||
SubList = lists:foldl(fun({SkipKey, SL}, Acc) ->
|
|
||||||
case {Acc, SkipKey} of
|
|
||||||
{null, SkipKey} when SkipKey >= Key ->
|
|
||||||
SL;
|
|
||||||
_ ->
|
|
||||||
Acc
|
|
||||||
end end,
|
|
||||||
null,
|
|
||||||
SkipList),
|
|
||||||
case SubList of
|
|
||||||
null ->
|
|
||||||
not_found;
|
|
||||||
SubList ->
|
|
||||||
case lists:keyfind(Key, 1, SubList) of
|
|
||||||
false ->
|
|
||||||
not_found;
|
|
||||||
{Key, V} ->
|
|
||||||
{Key, V}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
skiplist_range(SkipList, Start, End) ->
|
|
||||||
R = lists:foldl(fun({Mark, SL}, {PassedStart, PassedEnd, Acc, PrevList}) ->
|
|
||||||
|
|
||||||
case {PassedStart, PassedEnd} of
|
|
||||||
{true, true} ->
|
|
||||||
{true, true, Acc, null};
|
|
||||||
{false, false} ->
|
|
||||||
case Start > Mark of
|
|
||||||
true ->
|
|
||||||
{false, false, Acc, SL};
|
|
||||||
false ->
|
|
||||||
RHS = splitlist_start(Start, PrevList ++ SL),
|
|
||||||
case leveled_codec:endkey_passed(End, Mark) of
|
|
||||||
true ->
|
|
||||||
EL = splitlist_end(End, RHS),
|
|
||||||
{true, true, EL, null};
|
|
||||||
false ->
|
|
||||||
{true, false, RHS, null}
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
{true, false} ->
|
|
||||||
case leveled_codec:endkey_passed(End, Mark) of
|
|
||||||
true ->
|
|
||||||
EL = splitlist_end(End, SL),
|
|
||||||
{true, true, Acc ++ EL, null};
|
|
||||||
false ->
|
|
||||||
{true, false, Acc ++ SL, null}
|
|
||||||
end
|
|
||||||
end end,
|
|
||||||
|
|
||||||
{false, false, [], []},
|
|
||||||
SkipList),
|
|
||||||
{_Bool1, _Bool2, SubList, _PrevList} = R,
|
|
||||||
SubList.
|
|
||||||
|
|
||||||
splitlist_start(StartKey, SL) ->
|
|
||||||
{_LHS, RHS} = lists:splitwith(fun({K, _V}) -> K < StartKey end, SL),
|
|
||||||
RHS.
|
|
||||||
|
|
||||||
splitlist_end(EndKey, SL) ->
|
|
||||||
{LHS, _RHS} = lists:splitwith(fun({K, _V}) ->
|
|
||||||
not leveled_codec:endkey_passed(EndKey, K)
|
|
||||||
end,
|
|
||||||
SL),
|
|
||||||
LHS.
|
|
||||||
|
|
||||||
|
|
||||||
|
merge_trees(StartKey, EndKey, SkipListList, LevelMinus1) ->
|
||||||
|
lists:foldl(fun(SkipList, Acc) ->
|
||||||
|
R = leveled_skiplist:to_range(SkipList,
|
||||||
|
StartKey,
|
||||||
|
EndKey),
|
||||||
|
lists:ukeymerge(1, Acc, R) end,
|
||||||
|
[],
|
||||||
|
[LevelMinus1|lists:reverse(SkipListList)]).
|
||||||
|
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
%%% Internal Functions
|
%%% Internal Functions
|
||||||
|
@ -320,24 +158,6 @@ hash_to_slot(Key) ->
|
||||||
H = erlang:phash2(Key),
|
H = erlang:phash2(Key),
|
||||||
{H bsr element(2, ?SLOT_WIDTH), H band (element(1, ?SLOT_WIDTH) - 1)}.
|
{H bsr element(2, ?SLOT_WIDTH), H band (element(1, ?SLOT_WIDTH) - 1)}.
|
||||||
|
|
||||||
merge_nexttree(Tree, TreeAcc, StartKey, EndKey) ->
|
|
||||||
Iter = gb_trees:iterator_from(StartKey, Tree),
|
|
||||||
merge_nexttree(Iter, TreeAcc, EndKey).
|
|
||||||
|
|
||||||
merge_nexttree(Iter, TreeAcc, EndKey) ->
|
|
||||||
case gb_trees:next(Iter) of
|
|
||||||
none ->
|
|
||||||
TreeAcc;
|
|
||||||
{Key, Value, NewIter} ->
|
|
||||||
case leveled_codec:endkey_passed(EndKey, Key) of
|
|
||||||
true ->
|
|
||||||
TreeAcc;
|
|
||||||
false ->
|
|
||||||
merge_nexttree(NewIter,
|
|
||||||
gb_trees:enter(Key, Value, TreeAcc),
|
|
||||||
EndKey)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
%%% Test
|
%%% Test
|
||||||
|
@ -348,27 +168,21 @@ merge_nexttree(Iter, TreeAcc, EndKey) ->
|
||||||
generate_randomkeys(Seqn, Count, BucketRangeLow, BucketRangeHigh) ->
|
generate_randomkeys(Seqn, Count, BucketRangeLow, BucketRangeHigh) ->
|
||||||
generate_randomkeys(Seqn,
|
generate_randomkeys(Seqn,
|
||||||
Count,
|
Count,
|
||||||
gb_trees:empty(),
|
leveled_skiplist:empty(),
|
||||||
BucketRangeLow,
|
BucketRangeLow,
|
||||||
BucketRangeHigh).
|
BucketRangeHigh).
|
||||||
|
|
||||||
generate_randomkeys(_Seqn, 0, Acc, _BucketLow, _BucketHigh) ->
|
generate_randomkeys(_Seqn, 0, Acc, _BucketLow, _BucketHigh) ->
|
||||||
Acc;
|
Acc;
|
||||||
generate_randomkeys(Seqn, Count, Acc, BucketLow, BRange) ->
|
generate_randomkeys(Seqn, Count, Acc, BucketLow, BRange) ->
|
||||||
BNumber =
|
BNumber = string:right(integer_to_list(BucketLow + random:uniform(BRange)),
|
||||||
case BRange of
|
4, $0),
|
||||||
0 ->
|
|
||||||
string:right(integer_to_list(BucketLow), 4, $0);
|
|
||||||
_ ->
|
|
||||||
BRand = random:uniform(BRange),
|
|
||||||
string:right(integer_to_list(BucketLow + BRand), 4, $0)
|
|
||||||
end,
|
|
||||||
KNumber = string:right(integer_to_list(random:uniform(1000)), 4, $0),
|
KNumber = string:right(integer_to_list(random:uniform(1000)), 4, $0),
|
||||||
{K, V} = {{o, "Bucket" ++ BNumber, "Key" ++ KNumber, null},
|
{K, V} = {{o, "Bucket" ++ BNumber, "Key" ++ KNumber, null},
|
||||||
{Seqn, {active, infinity}, null}},
|
{Seqn, {active, infinity}, null}},
|
||||||
generate_randomkeys(Seqn + 1,
|
generate_randomkeys(Seqn + 1,
|
||||||
Count - 1,
|
Count - 1,
|
||||||
gb_trees:enter(K, V, Acc),
|
leveled_skiplist:enter(K, V, Acc),
|
||||||
BucketLow,
|
BucketLow,
|
||||||
BRange).
|
BRange).
|
||||||
|
|
||||||
|
@ -387,29 +201,29 @@ compare_method_test() ->
|
||||||
?assertMatch(32000, SQN),
|
?assertMatch(32000, SQN),
|
||||||
?assertMatch(true, Size =< 32000),
|
?assertMatch(true, Size =< 32000),
|
||||||
|
|
||||||
TestList = gb_trees:to_list(generate_randomkeys(1, 2000, 1, 800)),
|
TestList = leveled_skiplist:to_list(generate_randomkeys(1, 2000, 1, 800)),
|
||||||
|
|
||||||
S0 = lists:foldl(fun({Key, _V}, Acc) ->
|
S0 = lists:foldl(fun({Key, _V}, Acc) ->
|
||||||
R0 = lists:foldr(fun(Tree, {Found, KV}) ->
|
R0 = lists:foldr(fun(Tree, {Found, KV}) ->
|
||||||
case Found of
|
case Found of
|
||||||
true ->
|
true ->
|
||||||
{true, KV};
|
{true, KV};
|
||||||
false ->
|
false ->
|
||||||
L0 = gb_trees:lookup(Key, Tree),
|
L0 = leveled_skiplist:lookup(Key, Tree),
|
||||||
case L0 of
|
case L0 of
|
||||||
none ->
|
none ->
|
||||||
{false, not_found};
|
{false, not_found};
|
||||||
{value, Value} ->
|
{value, Value} ->
|
||||||
{true, {Key, Value}}
|
{true, {Key, Value}}
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end,
|
end
|
||||||
{false, not_found},
|
end,
|
||||||
TreeList),
|
{false, not_found},
|
||||||
[R0|Acc]
|
TreeList),
|
||||||
end,
|
[R0|Acc]
|
||||||
[],
|
end,
|
||||||
TestList),
|
[],
|
||||||
|
TestList),
|
||||||
|
|
||||||
S1 = lists:foldl(fun({Key, _V}, Acc) ->
|
S1 = lists:foldl(fun({Key, _V}, Acc) ->
|
||||||
R0 = check_levelzero(Key, Index, TreeList),
|
R0 = check_levelzero(Key, Index, TreeList),
|
||||||
|
@ -429,221 +243,25 @@ compare_method_test() ->
|
||||||
P = leveled_codec:endkey_passed(EndKey, K),
|
P = leveled_codec:endkey_passed(EndKey, K),
|
||||||
case {K, P} of
|
case {K, P} of
|
||||||
{K, false} when K >= StartKey ->
|
{K, false} when K >= StartKey ->
|
||||||
gb_trees:enter(K, V, Acc);
|
leveled_skiplist:enter(K, V, Acc);
|
||||||
_ ->
|
_ ->
|
||||||
Acc
|
Acc
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
gb_trees:empty(),
|
leveled_skiplist:empty(),
|
||||||
DumpList),
|
DumpList),
|
||||||
Sz0 = gb_trees:size(Q0),
|
Sz0 = leveled_skiplist:size(Q0),
|
||||||
io:format(user, "Crude method took ~w microseconds resulting in tree of "
|
io:format("Crude method took ~w microseconds resulting in tree of " ++
|
||||||
++ "size ~w~n",
|
"size ~w~n",
|
||||||
[timer:now_diff(os:timestamp(), SWa), Sz0]),
|
[timer:now_diff(os:timestamp(), SWa), Sz0]),
|
||||||
SWb = os:timestamp(),
|
SWb = os:timestamp(),
|
||||||
Q1 = merge_trees(StartKey, EndKey, TreeList, gb_trees:empty()),
|
Q1 = merge_trees(StartKey, EndKey, TreeList, leveled_skiplist:empty()),
|
||||||
Sz1 = gb_trees:size(Q1),
|
Sz1 = length(Q1),
|
||||||
io:format(user, "Merge method took ~w microseconds resulting in tree of "
|
io:format("Merge method took ~w microseconds resulting in tree of " ++
|
||||||
++ "size ~w~n",
|
"size ~w~n",
|
||||||
[timer:now_diff(os:timestamp(), SWb), Sz1]),
|
[timer:now_diff(os:timestamp(), SWb), Sz1]),
|
||||||
?assertMatch(Sz0, Sz1).
|
?assertMatch(Sz0, Sz1).
|
||||||
|
|
||||||
skiplist_test() ->
|
|
||||||
KL = gb_trees:to_list(generate_randomkeys(1, 4000, 1, 200)),
|
|
||||||
SWaD = os:timestamp(),
|
|
||||||
_D = lists:foldl(fun({K, V}, AccD) -> dict:store(K, V, AccD) end,
|
|
||||||
dict:new(),
|
|
||||||
KL),
|
|
||||||
io:format(user, "Loading dict with 4000 keys in ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWaD)]),
|
|
||||||
|
|
||||||
SWa = os:timestamp(),
|
|
||||||
SkipList = skiplist_generate(KL),
|
|
||||||
io:format(user, "Generating skip list with 4000 keys in ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWa)]),
|
|
||||||
|
|
||||||
CheckList1 = lists:sublist(KL, 1200, 100),
|
|
||||||
CheckList2 = lists:sublist(KL, 1600, 100),
|
|
||||||
CheckList3 = lists:sublist(KL, 2000, 100),
|
|
||||||
CheckList4 = lists:sublist(KL, 2400, 100),
|
|
||||||
CheckList5 = lists:sublist(KL, 2800, 100),
|
|
||||||
CheckList6 = lists:sublist(KL, 1, 10),
|
|
||||||
CheckList7 = lists:nthtail(3800, KL),
|
|
||||||
CheckList8 = lists:sublist(KL, 3000, 1),
|
|
||||||
CheckAll = CheckList1 ++ CheckList2 ++ CheckList3 ++
|
|
||||||
CheckList4 ++ CheckList5 ++ CheckList6 ++ CheckList7,
|
|
||||||
|
|
||||||
SWb = os:timestamp(),
|
|
||||||
lists:foreach(fun({K, V}) ->
|
|
||||||
?assertMatch({K, V}, skiplist_get(SkipList, K))
|
|
||||||
end,
|
|
||||||
CheckAll),
|
|
||||||
io:format(user, "Finding 520 keys took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWb)]),
|
|
||||||
|
|
||||||
SWc = os:timestamp(),
|
|
||||||
KR1 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList1)),
|
|
||||||
element(1, lists:last(CheckList1))),
|
|
||||||
io:format("Result length ~w ~n", [length(KR1)]),
|
|
||||||
CompareL1 = length(lists:usort(CheckList1)),
|
|
||||||
?assertMatch(CompareL1, length(KR1)),
|
|
||||||
KR2 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList2)),
|
|
||||||
element(1, lists:last(CheckList2))),
|
|
||||||
CompareL2 = length(lists:usort(CheckList2)),
|
|
||||||
?assertMatch(CompareL2, length(KR2)),
|
|
||||||
KR3 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList3)),
|
|
||||||
element(1, lists:last(CheckList3))),
|
|
||||||
CompareL3 = length(lists:usort(CheckList3)),
|
|
||||||
?assertMatch(CompareL3, length(KR3)),
|
|
||||||
KR4 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList4)),
|
|
||||||
element(1, lists:last(CheckList4))),
|
|
||||||
CompareL4 = length(lists:usort(CheckList4)),
|
|
||||||
?assertMatch(CompareL4, length(KR4)),
|
|
||||||
KR5 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList5)),
|
|
||||||
element(1, lists:last(CheckList5))),
|
|
||||||
CompareL5 = length(lists:usort(CheckList5)),
|
|
||||||
?assertMatch(CompareL5, length(KR5)),
|
|
||||||
KR6 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList6)),
|
|
||||||
element(1, lists:last(CheckList6))),
|
|
||||||
CompareL6 = length(lists:usort(CheckList6)),
|
|
||||||
?assertMatch(CompareL6, length(KR6)),
|
|
||||||
KR7 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList7)),
|
|
||||||
element(1, lists:last(CheckList7))),
|
|
||||||
CompareL7 = length(lists:usort(CheckList7)),
|
|
||||||
?assertMatch(CompareL7, length(KR7)),
|
|
||||||
KR8 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, CheckList8)),
|
|
||||||
element(1, lists:last(CheckList8))),
|
|
||||||
CompareL8 = length(lists:usort(CheckList8)),
|
|
||||||
?assertMatch(CompareL8, length(KR8)),
|
|
||||||
|
|
||||||
KL_OOR1 = gb_trees:to_list(generate_randomkeys(1, 4, 201, 202)),
|
|
||||||
KR9 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, KL_OOR1)),
|
|
||||||
element(1, lists:last(KL_OOR1))),
|
|
||||||
?assertMatch([], KR9),
|
|
||||||
KL_OOR2 = gb_trees:to_list(generate_randomkeys(1, 4, 0, 0)),
|
|
||||||
KR10 = skiplist_range(SkipList,
|
|
||||||
element(1, lists:nth(1, KL_OOR2)),
|
|
||||||
element(1, lists:last(KL_OOR2))),
|
|
||||||
?assertMatch([], KR10),
|
|
||||||
|
|
||||||
io:format(user, "Finding 10 ranges took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWc)]),
|
|
||||||
|
|
||||||
AltKL = gb_trees:to_list(generate_randomkeys(1, 1000, 1, 200)),
|
|
||||||
SWd = os:timestamp(),
|
|
||||||
lists:foreach(fun({K, _V}) ->
|
|
||||||
skiplist_get(SkipList, K)
|
|
||||||
end,
|
|
||||||
AltKL),
|
|
||||||
io:format(user, "Finding 1000 mainly missing keys took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWd)]).
|
|
||||||
|
|
||||||
hash_index_test() ->
|
|
||||||
KeyCount = 4000,
|
|
||||||
SlotWidth = element(1, ?SLOT_WIDTH),
|
|
||||||
HI0 = new_index(),
|
|
||||||
MHI0 = new_index(),
|
|
||||||
KL0 = gb_trees:to_list(generate_randomkeys(1, KeyCount, 1, 200)),
|
|
||||||
CheckList1 = lists:sublist(KL0, 1200, 100),
|
|
||||||
CheckList2 = lists:sublist(KL0, 1600, 100),
|
|
||||||
CheckList3 = lists:sublist(KL0, 2000, 100),
|
|
||||||
CheckList4 = lists:sublist(KL0, 2400, 100),
|
|
||||||
CheckList5 = lists:sublist(KL0, 2800, 100),
|
|
||||||
CheckList6 = lists:sublist(KL0, 1, 10),
|
|
||||||
CheckList7 = lists:nthtail(3800, KL0),
|
|
||||||
CheckAll = CheckList1 ++ CheckList2 ++ CheckList3 ++
|
|
||||||
CheckList4 ++ CheckList5 ++ CheckList6 ++ CheckList7,
|
|
||||||
|
|
||||||
SWa = os:timestamp(),
|
|
||||||
{HashIndex1, SkipList1, _TC} =
|
|
||||||
lists:foldl(fun({K, V}, {HI, SL, C}) ->
|
|
||||||
{H, S} = hash_to_slot(K),
|
|
||||||
{UpdHI, UpdC} = addhash_to_index(HI, H, S, C),
|
|
||||||
UpdSL = skiplist_put(SL, K, V, H),
|
|
||||||
{UpdHI, UpdSL, UpdC} end,
|
|
||||||
{HI0, ?EMPTY_SKIPLIST, 0},
|
|
||||||
KL0),
|
|
||||||
io:format(user, "Dynamic load of skiplist took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWa)]),
|
|
||||||
|
|
||||||
{LL, LN} = lists:foldl(fun({K, SL}, {Count, Number}) ->
|
|
||||||
{Count + length(SL), Number + 1} end,
|
|
||||||
{0, 0},
|
|
||||||
SkipList1),
|
|
||||||
io:format(user,
|
|
||||||
"Skip list has ~w markers with total members of ~w~n",
|
|
||||||
[LN, LL]),
|
|
||||||
?assertMatch(true, LL / LN > ?SKIP_WIDTH / 2 ),
|
|
||||||
?assertMatch(true, LL / LN < ?SKIP_WIDTH * 2 ),
|
|
||||||
|
|
||||||
SWb = os:timestamp(),
|
|
||||||
lists:foreach(fun({K, V}) ->
|
|
||||||
?assertMatch({K, V},
|
|
||||||
skiplist_get(SkipList1, K))
|
|
||||||
end,
|
|
||||||
CheckAll),
|
|
||||||
io:format(user, "Fetching ~w keys from skiplist took ~w microseconds~n",
|
|
||||||
[KeyCount, timer:now_diff(os:timestamp(), SWb)]),
|
|
||||||
|
|
||||||
SWc = os:timestamp(),
|
|
||||||
{HI1, _C1} = lists:foldl(fun({K, _V}, {HI, C}) ->
|
|
||||||
{H, S} = hash_to_slot(K),
|
|
||||||
addhash_to_index(HI, H, S, C) end,
|
|
||||||
{HI0, 0},
|
|
||||||
KL0),
|
|
||||||
io:format(user, "Adding ~w keys to hashindex took ~w microseconds~n",
|
|
||||||
[KeyCount, timer:now_diff(os:timestamp(), SWc)]),
|
|
||||||
?assertMatch(SlotWidth, array:size(HI1)),
|
|
||||||
|
|
||||||
SWd = os:timestamp(),
|
|
||||||
{MHI1, TC1} = merge_indexes(HI1, MHI0, 0, 0),
|
|
||||||
io:format(user, "First merge to hashindex took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWd)]),
|
|
||||||
?assertMatch(SlotWidth, array:size(MHI1)),
|
|
||||||
|
|
||||||
KL1 = gb_trees:to_list(generate_randomkeys(1, KeyCount, 1, 200)),
|
|
||||||
|
|
||||||
SWe = os:timestamp(),
|
|
||||||
HI2 = new_index(),
|
|
||||||
{HI3, _C2} = lists:foldl(fun({K, _V}, {HI, C}) ->
|
|
||||||
{H, S} = hash_to_slot(K),
|
|
||||||
addhash_to_index(HI, H, S, C) end,
|
|
||||||
{HI2, 0},
|
|
||||||
KL1),
|
|
||||||
io:format(user, "Adding ~w keys to hashindex took ~w microseconds~n",
|
|
||||||
[KeyCount, timer:now_diff(os:timestamp(), SWe)]),
|
|
||||||
|
|
||||||
SWf = os:timestamp(),
|
|
||||||
{MHI2, TC2} = merge_indexes(HI3, MHI1, TC1, 1),
|
|
||||||
io:format(user, "Second merge to hashindex took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWf)]),
|
|
||||||
?assertMatch(SlotWidth, array:size(MHI2)),
|
|
||||||
|
|
||||||
SWg = os:timestamp(),
|
|
||||||
HI4 = new_index(),
|
|
||||||
{HI5, _C3} = lists:foldl(fun({K, _V}, {HI, C}) ->
|
|
||||||
{H, S} = hash_to_slot(K),
|
|
||||||
addhash_to_index(HI, H, S, C) end,
|
|
||||||
{HI4, 0},
|
|
||||||
KL1),
|
|
||||||
io:format(user, "Adding ~w keys to hashindex took ~w microseconds~n",
|
|
||||||
[KeyCount, timer:now_diff(os:timestamp(), SWg)]),
|
|
||||||
|
|
||||||
SWh = os:timestamp(),
|
|
||||||
{MHI3, _TC3} = merge_indexes(HI5, MHI2, TC2, 2),
|
|
||||||
io:format(user, "Third merge to hashindex took ~w microseconds~n",
|
|
||||||
[timer:now_diff(os:timestamp(), SWh)]),
|
|
||||||
?assertMatch(SlotWidth, array:size(MHI2)).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-endif.
|
-endif.
|
352
src/leveled_skiplist.erl
Normal file
352
src/leveled_skiplist.erl
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
%% -------- SKIPLIST ---------
|
||||||
|
%%
|
||||||
|
%% For storing small numbers of {K, V} pairs where reasonable insertion and
|
||||||
|
%% fetch times, but with fast support for flattening to a list or a sublist
|
||||||
|
%% within a certain key range
|
||||||
|
%%
|
||||||
|
%% Used instead of gb_trees to retain compatability of OTP16 (and Riak's
|
||||||
|
%% ongoing dependency on OTP16)
|
||||||
|
%%
|
||||||
|
%% Not a proper skip list. Only supports a single depth. Good enough for the
|
||||||
|
%% purposes of leveled. Also uses peculiar enkey_passed function within
|
||||||
|
%% leveled
|
||||||
|
|
||||||
|
-module(leveled_skiplist).
|
||||||
|
|
||||||
|
-include("include/leveled.hrl").
|
||||||
|
|
||||||
|
-export([
|
||||||
|
from_list/1,
|
||||||
|
to_list/1,
|
||||||
|
enter/3,
|
||||||
|
to_range/2,
|
||||||
|
to_range/3,
|
||||||
|
lookup/2,
|
||||||
|
empty/0,
|
||||||
|
size/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-define(SKIP_WIDTH, 32).
|
||||||
|
-define(INFINITY_KEY, {null, null, null, null, null}).
|
||||||
|
-define(EMPTY_SKIPLIST, [{?INFINITY_KEY, []}]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% SkipList API
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
enter(Key, Value, SkipList) ->
|
||||||
|
Hash = erlang:phash2(Key),
|
||||||
|
{MarkerKey, SubList} = lists:foldl(fun({Marker, SL}, Acc) ->
|
||||||
|
case Acc of
|
||||||
|
false ->
|
||||||
|
case Marker >= Key of
|
||||||
|
true ->
|
||||||
|
{Marker, SL};
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end end,
|
||||||
|
false,
|
||||||
|
SkipList),
|
||||||
|
case Hash rem ?SKIP_WIDTH of
|
||||||
|
0 ->
|
||||||
|
{LHS, RHS} = lists:splitwith(fun({K, _V}) -> K < Key end, SubList),
|
||||||
|
SkpL1 = lists:keyreplace(MarkerKey, 1, SkipList, {MarkerKey, RHS}),
|
||||||
|
SkpL2 = [{Key, lists:ukeysort(1, [{Key, Value}|LHS])}|SkpL1],
|
||||||
|
lists:ukeysort(1, SkpL2);
|
||||||
|
_ ->
|
||||||
|
UpdSubList = lists:ukeysort(1, [{Key, Value}|SubList]),
|
||||||
|
lists:keyreplace(MarkerKey, 1, SkipList, {MarkerKey, UpdSubList})
|
||||||
|
end.
|
||||||
|
|
||||||
|
from_list(UnsortedKVL) ->
|
||||||
|
KVL = lists:ukeysort(1, UnsortedKVL),
|
||||||
|
Slots = length(KVL) div ?SKIP_WIDTH,
|
||||||
|
SkipList0 = lists:map(fun(X) ->
|
||||||
|
N = X * ?SKIP_WIDTH,
|
||||||
|
{K, _V} = lists:nth(N, KVL),
|
||||||
|
{K, lists:sublist(KVL,
|
||||||
|
N - ?SKIP_WIDTH + 1,
|
||||||
|
?SKIP_WIDTH)}
|
||||||
|
end,
|
||||||
|
lists:seq(1, length(KVL) div ?SKIP_WIDTH)),
|
||||||
|
case Slots * ?SKIP_WIDTH < length(KVL) of
|
||||||
|
true ->
|
||||||
|
{LastK, _V} = lists:last(KVL),
|
||||||
|
SkipList0 ++ [{LastK, lists:nthtail(Slots * ?SKIP_WIDTH, KVL)}];
|
||||||
|
false ->
|
||||||
|
SkipList0
|
||||||
|
end.
|
||||||
|
|
||||||
|
lookup(Key, SkipList) ->
|
||||||
|
SubList = lists:foldl(fun({SkipKey, SL}, Acc) ->
|
||||||
|
case {Acc, SkipKey} of
|
||||||
|
{null, SkipKey} when SkipKey >= Key ->
|
||||||
|
SL;
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end end,
|
||||||
|
null,
|
||||||
|
SkipList),
|
||||||
|
case SubList of
|
||||||
|
null ->
|
||||||
|
none;
|
||||||
|
SubList ->
|
||||||
|
case lists:keyfind(Key, 1, SubList) of
|
||||||
|
false ->
|
||||||
|
none;
|
||||||
|
{Key, V} ->
|
||||||
|
{value, V}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
to_list(SkipList) ->
|
||||||
|
lists:foldl(fun({_Mark, SL}, Acc) -> Acc ++ SL end, [], SkipList).
|
||||||
|
|
||||||
|
%% Rather than support iterator_from like gb_trees, will just an output a key
|
||||||
|
%% sorted list for the desired range, which can the be iterated over as normal
|
||||||
|
to_range(SkipList, Start) ->
|
||||||
|
to_range(SkipList, Start, ?INFINITY_KEY).
|
||||||
|
|
||||||
|
to_range(SkipList, Start, End) ->
|
||||||
|
R = lists:foldl(fun({Mark, SL}, {PassedStart, PassedEnd, Acc, PrevList}) ->
|
||||||
|
|
||||||
|
case {PassedStart, PassedEnd} of
|
||||||
|
{true, true} ->
|
||||||
|
{true, true, Acc, null};
|
||||||
|
{false, false} ->
|
||||||
|
case Start > Mark of
|
||||||
|
true ->
|
||||||
|
{false, false, Acc, SL};
|
||||||
|
false ->
|
||||||
|
RHS = splitlist_start(Start, PrevList ++ SL),
|
||||||
|
case leveled_codec:endkey_passed(End, Mark) of
|
||||||
|
true ->
|
||||||
|
EL = splitlist_end(End, RHS),
|
||||||
|
{true, true, EL, null};
|
||||||
|
false ->
|
||||||
|
{true, false, RHS, null}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
{true, false} ->
|
||||||
|
case leveled_codec:endkey_passed(End, Mark) of
|
||||||
|
true ->
|
||||||
|
EL = splitlist_end(End, SL),
|
||||||
|
{true, true, Acc ++ EL, null};
|
||||||
|
false ->
|
||||||
|
{true, false, Acc ++ SL, null}
|
||||||
|
end
|
||||||
|
end end,
|
||||||
|
|
||||||
|
{false, false, [], []},
|
||||||
|
SkipList),
|
||||||
|
{_Bool1, _Bool2, SubList, _PrevList} = R,
|
||||||
|
SubList.
|
||||||
|
|
||||||
|
empty() ->
|
||||||
|
?EMPTY_SKIPLIST.
|
||||||
|
|
||||||
|
size(SkipList) ->
|
||||||
|
lists:foldl(fun({_Mark, SL}, Acc) -> length(SL) + Acc end, 0, SkipList).
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% Internal Functions
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
splitlist_start(StartKey, SL) ->
|
||||||
|
{_LHS, RHS} = lists:splitwith(fun({K, _V}) -> K < StartKey end, SL),
|
||||||
|
RHS.
|
||||||
|
|
||||||
|
splitlist_end(EndKey, SL) ->
|
||||||
|
{LHS, _RHS} = lists:splitwith(fun({K, _V}) ->
|
||||||
|
not leveled_codec:endkey_passed(EndKey, K)
|
||||||
|
end,
|
||||||
|
SL),
|
||||||
|
LHS.
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% Test
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
|
||||||
|
generate_randomkeys(Seqn, Count, BucketRangeLow, BucketRangeHigh) ->
|
||||||
|
generate_randomkeys(Seqn,
|
||||||
|
Count,
|
||||||
|
gb_trees:empty(),
|
||||||
|
BucketRangeLow,
|
||||||
|
BucketRangeHigh).
|
||||||
|
|
||||||
|
generate_randomkeys(_Seqn, 0, Acc, _BucketLow, _BucketHigh) ->
|
||||||
|
Acc;
|
||||||
|
generate_randomkeys(Seqn, Count, Acc, BucketLow, BRange) ->
|
||||||
|
BNumber =
|
||||||
|
case BRange of
|
||||||
|
0 ->
|
||||||
|
string:right(integer_to_list(BucketLow), 4, $0);
|
||||||
|
_ ->
|
||||||
|
BRand = random:uniform(BRange),
|
||||||
|
string:right(integer_to_list(BucketLow + BRand), 4, $0)
|
||||||
|
end,
|
||||||
|
KNumber = string:right(integer_to_list(random:uniform(1000)), 4, $0),
|
||||||
|
{K, V} = {{o, "Bucket" ++ BNumber, "Key" ++ KNumber, null},
|
||||||
|
{Seqn, {active, infinity}, null}},
|
||||||
|
generate_randomkeys(Seqn + 1,
|
||||||
|
Count - 1,
|
||||||
|
gb_trees:enter(K, V, Acc),
|
||||||
|
BucketLow,
|
||||||
|
BRange).
|
||||||
|
|
||||||
|
skiplist_test() ->
|
||||||
|
KL = gb_trees:to_list(generate_randomkeys(1, 4000, 1, 200)),
|
||||||
|
SWaD = os:timestamp(),
|
||||||
|
_D = lists:foldl(fun({K, V}, AccD) -> dict:store(K, V, AccD) end,
|
||||||
|
dict:new(),
|
||||||
|
KL),
|
||||||
|
io:format(user, "Loading dict with 4000 keys in ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWaD)]),
|
||||||
|
|
||||||
|
SWaGSL = os:timestamp(),
|
||||||
|
SkipList = from_list(KL),
|
||||||
|
io:format(user, "Generating skip list with 4000 keys in ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWaGSL)]),
|
||||||
|
SWaDSL = os:timestamp(),
|
||||||
|
SkipList1 =
|
||||||
|
lists:foldl(fun({K, V}, SL) ->
|
||||||
|
enter(K, V, SL)
|
||||||
|
end,
|
||||||
|
?EMPTY_SKIPLIST,
|
||||||
|
KL),
|
||||||
|
io:format(user, "Dynamic load of skiplist took ~w microseconds~n~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWaDSL)]),
|
||||||
|
|
||||||
|
io:format(user, "~nRunning timing tests for generated skiplist:~n", []),
|
||||||
|
skiplist_timingtest(KL, SkipList),
|
||||||
|
|
||||||
|
io:format(user, "~nRunning timing tests for dynamic skiplist:~n", []),
|
||||||
|
skiplist_timingtest(KL, SkipList1),
|
||||||
|
io:format(user, "~n", []).
|
||||||
|
|
||||||
|
|
||||||
|
skiplist_timingtest(KL, SkipList) ->
|
||||||
|
io:format(user, "Timing tests on skiplist of size ~w~n",
|
||||||
|
[leveled_skiplist:size(SkipList)]),
|
||||||
|
CheckList1 = lists:sublist(KL, 1200, 100),
|
||||||
|
CheckList2 = lists:sublist(KL, 1600, 100),
|
||||||
|
CheckList3 = lists:sublist(KL, 2000, 100),
|
||||||
|
CheckList4 = lists:sublist(KL, 2400, 100),
|
||||||
|
CheckList5 = lists:sublist(KL, 2800, 100),
|
||||||
|
CheckList6 = lists:sublist(KL, 1, 10),
|
||||||
|
CheckList7 = lists:nthtail(3800, KL),
|
||||||
|
CheckList8 = lists:sublist(KL, 3000, 1),
|
||||||
|
CheckAll = CheckList1 ++ CheckList2 ++ CheckList3 ++
|
||||||
|
CheckList4 ++ CheckList5 ++ CheckList6 ++ CheckList7,
|
||||||
|
|
||||||
|
SWb = os:timestamp(),
|
||||||
|
lists:foreach(fun({K, V}) ->
|
||||||
|
?assertMatch({value, V}, lookup(K, SkipList))
|
||||||
|
end,
|
||||||
|
CheckAll),
|
||||||
|
io:format(user, "Finding 520 keys took ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWb)]),
|
||||||
|
|
||||||
|
SWc = os:timestamp(),
|
||||||
|
KR1 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList1)),
|
||||||
|
element(1, lists:last(CheckList1))),
|
||||||
|
io:format("Result length ~w ~n", [length(KR1)]),
|
||||||
|
CompareL1 = length(lists:usort(CheckList1)),
|
||||||
|
?assertMatch(CompareL1, length(KR1)),
|
||||||
|
KR2 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList2)),
|
||||||
|
element(1, lists:last(CheckList2))),
|
||||||
|
CompareL2 = length(lists:usort(CheckList2)),
|
||||||
|
?assertMatch(CompareL2, length(KR2)),
|
||||||
|
KR3 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList3)),
|
||||||
|
element(1, lists:last(CheckList3))),
|
||||||
|
CompareL3 = length(lists:usort(CheckList3)),
|
||||||
|
?assertMatch(CompareL3, length(KR3)),
|
||||||
|
KR4 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList4)),
|
||||||
|
element(1, lists:last(CheckList4))),
|
||||||
|
CompareL4 = length(lists:usort(CheckList4)),
|
||||||
|
?assertMatch(CompareL4, length(KR4)),
|
||||||
|
KR5 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList5)),
|
||||||
|
element(1, lists:last(CheckList5))),
|
||||||
|
CompareL5 = length(lists:usort(CheckList5)),
|
||||||
|
?assertMatch(CompareL5, length(KR5)),
|
||||||
|
KR6 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList6)),
|
||||||
|
element(1, lists:last(CheckList6))),
|
||||||
|
CompareL6 = length(lists:usort(CheckList6)),
|
||||||
|
?assertMatch(CompareL6, length(KR6)),
|
||||||
|
KR7 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList7)),
|
||||||
|
element(1, lists:last(CheckList7))),
|
||||||
|
CompareL7 = length(lists:usort(CheckList7)),
|
||||||
|
?assertMatch(CompareL7, length(KR7)),
|
||||||
|
KR8 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, CheckList8)),
|
||||||
|
element(1, lists:last(CheckList8))),
|
||||||
|
CompareL8 = length(lists:usort(CheckList8)),
|
||||||
|
?assertMatch(CompareL8, length(KR8)),
|
||||||
|
|
||||||
|
KL_OOR1 = gb_trees:to_list(generate_randomkeys(1, 4, 201, 202)),
|
||||||
|
KR9 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, KL_OOR1)),
|
||||||
|
element(1, lists:last(KL_OOR1))),
|
||||||
|
?assertMatch([], KR9),
|
||||||
|
KL_OOR2 = gb_trees:to_list(generate_randomkeys(1, 4, 0, 0)),
|
||||||
|
KR10 = to_range(SkipList,
|
||||||
|
element(1, lists:nth(1, KL_OOR2)),
|
||||||
|
element(1, lists:last(KL_OOR2))),
|
||||||
|
?assertMatch([], KR10),
|
||||||
|
|
||||||
|
io:format(user, "Finding 10 ranges took ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWc)]),
|
||||||
|
|
||||||
|
AltKL1 = gb_trees:to_list(generate_randomkeys(1, 1000, 1, 200)),
|
||||||
|
SWd = os:timestamp(),
|
||||||
|
lists:foreach(fun({K, _V}) ->
|
||||||
|
lookup(K, SkipList)
|
||||||
|
end,
|
||||||
|
AltKL1),
|
||||||
|
io:format(user, "Getting 1000 mainly missing keys took ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWd)]),
|
||||||
|
AltKL2 = gb_trees:to_list(generate_randomkeys(1, 1000, 201, 300)),
|
||||||
|
SWe = os:timestamp(),
|
||||||
|
lists:foreach(fun({K, _V}) ->
|
||||||
|
none = lookup(K, SkipList)
|
||||||
|
end,
|
||||||
|
AltKL2),
|
||||||
|
io:format(user, "Getting 1000 missing keys above range took ~w " ++
|
||||||
|
"microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWe)]),
|
||||||
|
AltKL3 = gb_trees:to_list(generate_randomkeys(1, 1000, 0, 0)),
|
||||||
|
SWf = os:timestamp(),
|
||||||
|
lists:foreach(fun({K, _V}) ->
|
||||||
|
none = lookup(K, SkipList)
|
||||||
|
end,
|
||||||
|
AltKL3),
|
||||||
|
io:format(user, "Getting 1000 missing keys below range took ~w " ++
|
||||||
|
"microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWf)]),
|
||||||
|
|
||||||
|
SWg = os:timestamp(),
|
||||||
|
FlatList = to_list(SkipList),
|
||||||
|
io:format(user, "Flattening skiplist took ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), SWg)]),
|
||||||
|
?assertMatch(KL, FlatList).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-endif.
|
Loading…
Add table
Add a link
Reference in a new issue