From 03d025d58123e8303dea2e46ca3b2d7b23bc8c4c Mon Sep 17 00:00:00 2001 From: martinsumner Date: Fri, 25 Nov 2016 14:50:13 +0000 Subject: [PATCH] Replace ledger-side gb_trees Try to make minimal change to replace gb_trees with gb_tree API-like skiplists --- src/leveled_bookie.erl | 26 +-- src/leveled_inker.erl | 2 +- src/leveled_penciller.erl | 161 +++++++------ src/leveled_pmem.erl | 474 ++++---------------------------------- src/leveled_skiplist.erl | 352 ++++++++++++++++++++++++++++ 5 files changed, 490 insertions(+), 525 deletions(-) create mode 100644 src/leveled_skiplist.erl diff --git a/src/leveled_bookie.erl b/src/leveled_bookie.erl index 46b4a29..d39ff84 100644 --- a/src/leveled_bookie.erl +++ b/src/leveled_bookie.erl @@ -149,7 +149,7 @@ -record(state, {inker :: pid(), penciller :: pid(), cache_size :: integer(), - ledger_cache :: gb_trees:tree(), + ledger_cache :: list(), % a skiplist is_snapshot :: boolean(), slow_offer = false :: boolean()}). @@ -233,14 +233,14 @@ init([Opts]) -> {ok, #state{inker=Inker, penciller=Penciller, cache_size=CacheSize, - ledger_cache=gb_trees:empty(), + ledger_cache=leveled_skiplist:empty(), is_snapshot=false}}; Bookie -> {ok, {Penciller, LedgerCache}, Inker} = book_snapshotstore(Bookie, self(), ?SNAPSHOT_TIMEOUT), ok = leveled_penciller:pcl_loadsnapshot(Penciller, - gb_trees:empty()), + leveled_skiplist:empty()), leveled_log:log("B0002", [Inker, Penciller]), {ok, #state{penciller=Penciller, inker=Inker, @@ -431,7 +431,7 @@ bucket_stats(State, Bucket, Tag) -> {LedgerSnapshot, LedgerCache}, _JournalSnapshot} = snapshot_store(State, ledger), Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), StartKey = leveled_codec:to_ledgerkey(Bucket, null, Tag), @@ -454,7 +454,7 @@ binary_bucketlist(State, Tag, {FoldBucketsFun, InitAcc}) -> {LedgerSnapshot, LedgerCache}, _JournalSnapshot} = snapshot_store(State, ledger), Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), BucketAcc = get_nextbucket(null, @@ -509,7 +509,7 @@ index_query(State, {B, null} end, Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), StartKey = leveled_codec:to_ledgerkey(Bucket, @@ -551,7 +551,7 @@ hashtree_query(State, Tag, JournalCheck) -> {LedgerSnapshot, LedgerCache}, JournalSnapshot} = snapshot_store(State, SnapType), Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), StartKey = leveled_codec:to_ledgerkey(null, null, Tag), @@ -602,7 +602,7 @@ foldobjects(State, Tag, StartKey, EndKey, FoldObjectsFun) -> {FoldObjectsFun, []} end, Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), AccFun = accumulate_objects(FoldFun, JournalSnapshot, Tag), @@ -623,7 +623,7 @@ bucketkey_query(State, Tag, Bucket, {FoldKeysFun, InitAcc}) -> {LedgerSnapshot, LedgerCache}, _JournalSnapshot} = snapshot_store(State, ledger), Folder = fun() -> - leveled_log:log("B0004", [gb_trees:size(LedgerCache)]), + leveled_log:log("B0004", [leveled_skiplist:size(LedgerCache)]), ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot, LedgerCache), SK = leveled_codec:to_ledgerkey(Bucket, null, Tag), @@ -697,7 +697,7 @@ startup(InkerOpts, PencillerOpts) -> fetch_head(Key, Penciller, LedgerCache) -> - case gb_trees:lookup(Key, LedgerCache) of + case leveled_skiplist:lookup(Key, LedgerCache) of {value, Head} -> Head; none -> @@ -863,18 +863,18 @@ preparefor_ledgercache(_Type, LedgerKey, SQN, Obj, Size, {IndexSpecs, TTL}) -> 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, Changes). maybepush_ledgercache(MaxCacheSize, Cache, Penciller) -> - CacheSize = gb_trees:size(Cache), + CacheSize = leveled_skiplist:size(Cache), TimeToPush = maybe_withjitter(CacheSize, MaxCacheSize), if TimeToPush -> case leveled_penciller:pcl_pushmem(Penciller, Cache) of ok -> - {ok, gb_trees:empty()}; + {ok, leveled_skiplist:empty()}; returned -> {returned, Cache} end; diff --git a/src/leveled_inker.erl b/src/leveled_inker.erl index acaad5f..cb00883 100644 --- a/src/leveled_inker.erl +++ b/src/leveled_inker.erl @@ -633,7 +633,7 @@ load_from_sequence(MinSQN, FilterFun, Penciller, [{_LowSQN, FN, Pid}|Rest]) -> load_between_sequence(MinSQN, MaxSQN, FilterFun, Penciller, CDBpid, StartPos, FN, Rest) -> 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 {eof, {AccMinSQN, _AccMaxSQN, AccKL}} -> ok = push_to_penciller(Penciller, AccKL), diff --git a/src/leveled_penciller.erl b/src/leveled_penciller.erl index 5705a61..af90cbf 100644 --- a/src/leveled_penciller.erl +++ b/src/leveled_penciller.erl @@ -212,7 +212,7 @@ levelzero_pending = false :: boolean(), levelzero_constructor :: pid(), - levelzero_cache = [] :: list(), % a list of gb_trees + levelzero_cache = [] :: list(), % a list of skiplists levelzero_index :: array:array(), levelzero_size = 0 :: integer(), levelzero_maxcachesize :: integer(), @@ -220,7 +220,7 @@ is_snapshot = false :: boolean(), snapshot_fully_loaded = false :: boolean(), source_penciller :: pid(), - levelzero_astree :: gb_trees:tree(), + levelzero_astree :: list(), % skiplist ongoing_work = [] :: list(), work_backlog = false :: boolean()}). @@ -366,25 +366,24 @@ handle_call({fetch_keys, StartKey, EndKey, AccFun, InitAcc, MaxKeys}, _From, State=#state{snapshot_fully_loaded=Ready}) when Ready == true -> - L0AsTree = + L0AsList = case State#state.levelzero_astree of undefined -> leveled_pmem:merge_trees(StartKey, EndKey, State#state.levelzero_cache, - gb_trees:empty()); - Tree -> - Tree + leveled_skiplist:empty()); + List -> + List end, - L0iter = gb_trees:iterator(L0AsTree), SFTiter = initiate_rangequery_frommanifest(StartKey, EndKey, State#state.manifest), - Acc = keyfolder({L0iter, SFTiter}, + Acc = keyfolder({L0AsList, SFTiter}, {StartKey, EndKey}, {AccFun, InitAcc}, MaxKeys), - {reply, Acc, State#state{levelzero_astree = L0AsTree}}; + {reply, Acc, State#state{levelzero_astree = L0AsList}}; handle_call(work_for_clerk, From, State) -> {UpdState, Work} = return_work(State, From), {reply, Work, UpdState}; @@ -985,77 +984,73 @@ keyfolder(IMMiter, SFTiter, StartKey, EndKey, {AccFun, Acc}) -> keyfolder(_Iterators, _KeyRange, {_AccFun, Acc}, MaxKeys) when MaxKeys == 0 -> Acc; -keyfolder({null, SFTiter}, KeyRange, {AccFun, Acc}, MaxKeys) -> +keyfolder({[], SFTiter}, KeyRange, {AccFun, Acc}, MaxKeys) -> {StartKey, EndKey} = KeyRange, case find_nextkey(SFTiter, StartKey, EndKey) of no_more_keys -> Acc; {NxSFTiter, {SFTKey, SFTVal}} -> Acc1 = AccFun(SFTKey, SFTVal, Acc), - keyfolder({null, NxSFTiter}, KeyRange, {AccFun, Acc1}, MaxKeys - 1) + keyfolder({[], NxSFTiter}, KeyRange, {AccFun, Acc1}, MaxKeys - 1) end; -keyfolder({IMMiterator, SFTiterator}, KeyRange, {AccFun, Acc}, MaxKeys) -> +keyfolder({[{IMMKey, IMMVal}|NxIMMiterator], SFTiterator}, KeyRange, + {AccFun, Acc}, MaxKeys) -> {StartKey, EndKey} = KeyRange, - case gb_trees:next(IMMiterator) of - none -> - % 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 -> + case {IMMKey < StartKey, leveled_codec:endkey_passed(EndKey, IMMKey)} of + {true, _} -> + % 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 keyfolder({NxIMMiterator, SFTiterator}, KeyRange, {AccFun, Acc}, MaxKeys); - {IMMKey, IMMVal, NxIMMiterator} -> - case leveled_codec:endkey_passed(EndKey, IMMKey) of - true -> - % There are no more keys in-range in the in-memory - % iterator, so take action as if this iterator is empty - % (see above) - keyfolder({null, SFTiterator}, + {false, true} -> + % There are no more keys in-range in the in-memory + % iterator, so take action as if this iterator is empty + % (see above) + keyfolder({[], SFTiterator}, + KeyRange, + {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, - {AccFun, Acc}, - MaxKeys); - 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 + {AccFun, Acc1}, + MaxKeys - 1); + {NxSFTiterator, {SFTKey, SFTVal}} -> + % There is a next key, so need to know which is the + % next key between the two (and handle two keys + % with different sequence numbers). + case leveled_codec:key_dominates({IMMKey, + IMMVal}, + {SFTKey, + SFTVal}) of + left_hand_first -> Acc1 = AccFun(IMMKey, IMMVal, Acc), keyfolder({NxIMMiterator, SFTiterator}, KeyRange, {AccFun, Acc1}, MaxKeys - 1); - {NxSFTiterator, {SFTKey, SFTVal}} -> - % There is a next key, so need to know which is the - % next key between the two (and handle two keys - % with different sequence numbers). - case leveled_codec:key_dominates({IMMKey, - IMMVal}, - {SFTKey, - SFTVal}) of - left_hand_first -> - Acc1 = AccFun(IMMKey, IMMVal, Acc), - keyfolder({NxIMMiterator, SFTiterator}, - KeyRange, - {AccFun, Acc1}, - 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 + right_hand_first -> + Acc1 = AccFun(SFTKey, SFTVal, Acc), + keyfolder({[{IMMKey, IMMVal}|NxIMMiterator], + 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. @@ -1267,8 +1262,8 @@ confirm_delete_test() -> maybe_pause_push(PCL, KL) -> - T0 = gb_trees:empty(), - T1 = lists:foldl(fun({K, V}, Acc) -> gb_trees:enter(K, V, Acc) end, + T0 = leveled_skiplist:empty(), + T1 = lists:foldl(fun({K, V}, Acc) -> leveled_skiplist:enter(K, V, Acc) end, T0, KL), case pcl_pushmem(PCL, T1) of @@ -1335,7 +1330,7 @@ simple_server_test() -> SnapOpts = #penciller_options{start_snapshot = true, source_penciller = PCLr}, {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(Key2, pcl_fetch(PclSnap, {o,"Bucket0002", "Key0002", null})), ?assertMatch(Key3, pcl_fetch(PclSnap, {o,"Bucket0003", "Key0003", null})), @@ -1384,7 +1379,7 @@ simple_server_test() -> term_to_binary("Hello")), {ok, PclSnap2} = pcl_start(SnapOpts), - ok = pcl_loadsnapshot(PclSnap2, gb_trees:empty()), + ok = pcl_loadsnapshot(PclSnap2, leveled_skiplist:empty()), ?assertMatch(false, pcl_checksequencenumber(PclSnap2, {o, "Bucket0001", @@ -1543,16 +1538,16 @@ foldwithimm_simple_test() -> {3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]}, {5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]} ], - IMM0 = gb_trees:enter({o, "Bucket1", "Key6"}, - {7, {active, infinity}, null}, - gb_trees:empty()), - IMM1 = gb_trees:enter({o, "Bucket1", "Key1"}, - {8, {active, infinity}, null}, - IMM0), - IMM2 = gb_trees:enter({o, "Bucket1", "Key8"}, - {9, {active, infinity}, null}, - IMM1), - IMMiter = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM2), + IMM0 = leveled_skiplist:enter({o, "Bucket1", "Key6"}, + {7, {active, infinity}, null}, + leveled_skiplist:empty()), + IMM1 = leveled_skiplist:enter({o, "Bucket1", "Key1"}, + {8, {active, infinity}, null}, + IMM0), + IMM2 = leveled_skiplist:enter({o, "Bucket1", "Key8"}, + {9, {active, infinity}, null}, + IMM1), + IMMiter = leveled_skiplist:to_range(IMM2, {o, "Bucket1", "Key1"}), AccFun = fun(K, V, Acc) -> SQN = leveled_codec:strip_to_seqonly({K, V}), Acc ++ [{K, SQN}] end, Acc = keyfolder(IMMiter, @@ -1564,10 +1559,10 @@ foldwithimm_simple_test() -> {{o, "Bucket1", "Key5"}, 2}, {{o, "Bucket1", "Key6"}, 7}], Acc), - IMM1A = gb_trees:enter({o, "Bucket1", "Key1"}, - {8, {active, infinity}, null}, - gb_trees:empty()), - IMMiterA = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM1A), + IMM1A = leveled_skiplist:enter({o, "Bucket1", "Key1"}, + {8, {active, infinity}, null}, + leveled_skiplist:empty()), + IMMiterA = leveled_skiplist:to_range(IMM1A, {o, "Bucket1", "Key1"}), AccA = keyfolder(IMMiterA, QueryArray, {o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"}, @@ -1576,10 +1571,10 @@ foldwithimm_simple_test() -> {{o, "Bucket1", "Key3"}, 3}, {{o, "Bucket1", "Key5"}, 2}], AccA), - IMM3 = gb_trees:enter({o, "Bucket1", "Key4"}, - {10, {active, infinity}, null}, - IMM2), - IMMiterB = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM3), + IMM3 = leveled_skiplist:enter({o, "Bucket1", "Key4"}, + {10, {active, infinity}, null}, + IMM2), + IMMiterB = leveled_skiplist:to_range(IMM3, {o, "Bucket1", "Key1"}), AccB = keyfolder(IMMiterB, QueryArray, {o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"}, @@ -1594,7 +1589,7 @@ create_file_test() -> Filename = "../test/new_file.sft", ok = file:write_file(Filename, term_to_binary("hello")), 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, {ok, SP, diff --git a/src/leveled_pmem.erl b/src/leveled_pmem.erl index 3c2a454..39dd0c6 100644 --- a/src/leveled_pmem.erl +++ b/src/leveled_pmem.erl @@ -51,20 +51,13 @@ -include_lib("eunit/include/eunit.hrl"). --define(SLOT_WIDTH, {2048, 11}). --define(SKIP_WIDTH, 32). --define(INFINITE_KEY, {null, null, null, null, null}). --define(EMPTY_SKIPLIST, [{?INFINITE_KEY, []}]). +-define(SLOT_WIDTH, {4096, 12}). + %%%============================================================================ %%% API %%%============================================================================ - - - - - add_to_index(L0Index, L0Size, LevelMinus1, LedgerSQN, TreeList) -> SW = os:timestamp(), SlotInTreeList = length(TreeList) + 1, @@ -83,7 +76,7 @@ add_to_index(L0Index, L0Size, LevelMinus1, LedgerSQN, TreeList) -> Count0, array:set(Slot, [{Hash, SlotInTreeList}|L], HashIndex)} end, - LM1List = gb_trees:to_list(LevelMinus1), + LM1List = leveled_skiplist:to_list(LevelMinus1), StartingT = {infinity, 0, L0Size, L0Index}, {MinSQN, MaxSQN, NewL0Size, UpdL0Index} = lists:foldl(FoldFun, StartingT, @@ -103,7 +96,7 @@ to_list(Slots, FetchFun) -> SlotList = lists:reverse(lists:seq(1, Slots)), FullList = lists:foldl(fun(Slot, Acc) -> Tree = FetchFun(Slot), - L = gb_trees:to_list(Tree), + L = leveled_skiplist:to_list(Tree), lists:ukeymerge(1, Acc, L) end, [], @@ -135,7 +128,7 @@ check_levelzero(Key, L0Index, TreeList) -> {Found, KV}; false -> CheckTree = lists:nth(SlotToCheck, TreeList), - case gb_trees:lookup(Key, CheckTree) of + case leveled_skiplist:lookup(Key, CheckTree) of none -> {Found, KV}; {value, Value} -> @@ -146,170 +139,15 @@ check_levelzero(Key, L0Index, TreeList) -> {false, not_found}, 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 @@ -320,24 +158,6 @@ hash_to_slot(Key) -> H = erlang:phash2(Key), {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 @@ -348,27 +168,21 @@ merge_nexttree(Iter, TreeAcc, EndKey) -> generate_randomkeys(Seqn, Count, BucketRangeLow, BucketRangeHigh) -> generate_randomkeys(Seqn, Count, - gb_trees:empty(), + leveled_skiplist: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, + BNumber = string:right(integer_to_list(BucketLow + random:uniform(BRange)), + 4, $0), 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), + leveled_skiplist:enter(K, V, Acc), BucketLow, BRange). @@ -387,29 +201,29 @@ compare_method_test() -> ?assertMatch(32000, SQN), ?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) -> - R0 = lists:foldr(fun(Tree, {Found, KV}) -> - case Found of - true -> - {true, KV}; - false -> - L0 = gb_trees:lookup(Key, Tree), - case L0 of - none -> - {false, not_found}; - {value, Value} -> - {true, {Key, Value}} - end + R0 = lists:foldr(fun(Tree, {Found, KV}) -> + case Found of + true -> + {true, KV}; + false -> + L0 = leveled_skiplist:lookup(Key, Tree), + case L0 of + none -> + {false, not_found}; + {value, Value} -> + {true, {Key, Value}} end - end, - {false, not_found}, - TreeList), - [R0|Acc] - end, - [], - TestList), + end + end, + {false, not_found}, + TreeList), + [R0|Acc] + end, + [], + TestList), S1 = lists:foldl(fun({Key, _V}, Acc) -> R0 = check_levelzero(Key, Index, TreeList), @@ -429,221 +243,25 @@ compare_method_test() -> P = leveled_codec:endkey_passed(EndKey, K), case {K, P} of {K, false} when K >= StartKey -> - gb_trees:enter(K, V, Acc); + leveled_skiplist:enter(K, V, Acc); _ -> Acc end end, - gb_trees:empty(), + leveled_skiplist:empty(), DumpList), - Sz0 = gb_trees:size(Q0), - io:format(user, "Crude method took ~w microseconds resulting in tree of " - ++ "size ~w~n", + Sz0 = leveled_skiplist:size(Q0), + io:format("Crude method took ~w microseconds resulting in tree of " ++ + "size ~w~n", [timer:now_diff(os:timestamp(), SWa), Sz0]), SWb = os:timestamp(), - Q1 = merge_trees(StartKey, EndKey, TreeList, gb_trees:empty()), - Sz1 = gb_trees:size(Q1), - io:format(user, "Merge method took ~w microseconds resulting in tree of " - ++ "size ~w~n", + Q1 = merge_trees(StartKey, EndKey, TreeList, leveled_skiplist:empty()), + Sz1 = length(Q1), + io:format("Merge method took ~w microseconds resulting in tree of " ++ + "size ~w~n", [timer:now_diff(os:timestamp(), SWb), 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. \ No newline at end of file diff --git a/src/leveled_skiplist.erl b/src/leveled_skiplist.erl new file mode 100644 index 0000000..803267d --- /dev/null +++ b/src/leveled_skiplist.erl @@ -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. \ No newline at end of file