mirror of https://github.com/apache/cassandra
(Accord) Fix:
- Attempt to fix CommandsForKey StackOverflowError (presumed to be reachable via SaveStatus->InternalStatus map returning null) - Bound recursion with SafeCommandStore.tryRecurse() - IndexOutOfBoundsException in CINTIA checkpoint list encoding bounds logic - Maintain MaxDecidedRX to save majority of work when deciding RX that are newer than those we have previously agreed - Introduce IntervalBTree and use in CommandsForRanges to limit time spent in critical section when there are millions of RX patch by Benedict; reviewed by Alex Petrov for CASSANDRA-20727
This commit is contained in:
parent
6d303e6843
commit
fadc64c17d
|
|
@ -1 +1 @@
|
|||
Subproject commit 154c05f50dcfe9cb69d14e4a67a137ecd0600e88
|
||||
Subproject commit 756f2a1f88f079e74c34fd8e0f3bb5aa98760bef
|
||||
|
|
@ -389,6 +389,14 @@ public class AccordCacheEntry<K, V> extends IntrusiveLinkedListNode
|
|||
return (V)unwrap();
|
||||
}
|
||||
|
||||
public V tryGetExclusive()
|
||||
{
|
||||
Invariants.require(owner == null || owner.commandStore == null || owner.commandStore.executor().isOwningThread());
|
||||
if (!isLoaded() || isShrunk())
|
||||
return null;
|
||||
return (V)unwrap();
|
||||
}
|
||||
|
||||
private Object unwrap()
|
||||
{
|
||||
return isNested() ? ((Nested)state).state : state;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import accord.api.Journal;
|
|||
import accord.api.RoutingKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.MaxDecidedRX;
|
||||
import accord.local.PreLoadContext;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
|
|
@ -1091,8 +1092,9 @@ public abstract class AccordTask<R> extends SubmittableTask implements Runnable,
|
|||
|
||||
void startInternal(Caches caches)
|
||||
{
|
||||
MaxDecidedRX maxDecidedRX = commandStore.unsafeGetMaxDecidedRX();
|
||||
summaryLoader = commandStore.commandsForRanges().loader(preLoadContext.primaryTxnId(), preLoadContext.keyHistory(), keysOrRanges);
|
||||
summaryLoader.forEachInCache(summary -> summaries.put(summary.txnId, summary), caches);
|
||||
summaryLoader.forEachInCache(keysOrRanges, summary -> summaries.put(summary.txnId, summary), caches);
|
||||
caches.commands().register(commandWatcher);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
|
|
@ -28,27 +29,121 @@ import java.util.function.Consumer;
|
|||
import java.util.function.UnaryOperator;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandSummaries;
|
||||
import accord.local.CommandSummaries.Summary;
|
||||
import accord.local.KeyHistory;
|
||||
import accord.local.MaxDecidedRX;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.primitives.AbstractRanges;
|
||||
import accord.primitives.AbstractUnseekableKeys;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.RangeRoute;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.Txn.Kind.Kinds;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekable;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.utils.Invariants;
|
||||
import org.agrona.collections.ObjectHashSet;
|
||||
import accord.utils.UnhandledEnum;
|
||||
import org.agrona.collections.Object2ObjectHashMap;
|
||||
import org.apache.cassandra.service.accord.api.TokenKey;
|
||||
import org.apache.cassandra.utils.btree.IntervalBTree;
|
||||
|
||||
import static accord.local.CommandSummaries.SummaryStatus.NOT_DIRECTLY_WITNESSED;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalEndWithKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalEndWithKeyStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalStartWithKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalStartWithKeyStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyEndWithIntervalEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyEndWithIntervalStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyStartWithIntervalEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyStartWithIntervalStart;
|
||||
|
||||
// TODO (expected): move to accord-core, merge with existing logic there
|
||||
public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements CommandSummaries.ByTxnIdSnapshot
|
||||
{
|
||||
static final IntervalComparators COMPARATORS = new IntervalComparators();
|
||||
static final IntervalKeyComparators KEY_COMPARATORS = new IntervalKeyComparators();
|
||||
static class TxnIdInterval implements Comparable<TxnIdInterval>
|
||||
{
|
||||
final RoutingKey start, end;
|
||||
final TxnId txnId;
|
||||
|
||||
TxnIdInterval(RoutingKey start, RoutingKey end, TxnId txnId)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.txnId = txnId;
|
||||
}
|
||||
|
||||
TxnIdInterval(Range range, TxnId txnId)
|
||||
{
|
||||
this.start = range.start();
|
||||
this.end = range.end();
|
||||
this.txnId = txnId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TxnIdInterval that)
|
||||
{
|
||||
int c = this.start.compareTo(that.start);
|
||||
if (c == 0) c = this.end.compareTo(that.end);
|
||||
if (c == 0) c = this.txnId.compareTo(that.txnId);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
static class IntervalComparators implements IntervalBTree.IntervalComparators<TxnIdInterval>
|
||||
{
|
||||
@Override public Comparator<TxnIdInterval> totalOrder() { return TxnIdInterval::compareTo; }
|
||||
@Override public Comparator<TxnIdInterval> startWithStartComparator() { return (a, b) -> a.start.compareTo(b.start); }
|
||||
@Override public Comparator<TxnIdInterval> startWithEndComparator() { return (a, b) -> a.start.compareTo(b.end); }
|
||||
@Override public Comparator<TxnIdInterval> endWithStartComparator() { return (a, b) -> a.end.compareTo(b.start); }
|
||||
@Override public Comparator<TxnIdInterval> endWithEndComparator() { return (a, b) -> a.end.compareTo(b.end); }
|
||||
}
|
||||
|
||||
static class IntervalKeyComparators implements IntervalBTree.IntervalComparators<Object>
|
||||
{
|
||||
@Override public Comparator<Object> totalOrder() { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public Comparator<Object> startWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == TxnIdInterval.class
|
||||
? intervalStartWithKeyStart(((TxnIdInterval) a).start.compareTo((RoutingKey)b))
|
||||
: keyStartWithIntervalStart(((RoutingKey)a).compareTo(((TxnIdInterval)b).start));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> startWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == TxnIdInterval.class
|
||||
? intervalStartWithKeyEnd(((TxnIdInterval)a).start.compareTo((RoutingKey)b))
|
||||
: keyStartWithIntervalEnd(((RoutingKey)a).compareTo(((TxnIdInterval)b).end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> endWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == TxnIdInterval.class
|
||||
? intervalEndWithKeyStart(((TxnIdInterval)a).end.compareTo((RoutingKey)b))
|
||||
: keyEndWithIntervalStart(((RoutingKey)a).compareTo(((TxnIdInterval)b).start));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> endWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == TxnIdInterval.class
|
||||
? intervalEndWithKeyEnd(((TxnIdInterval)a).end.compareTo((RoutingKey)b))
|
||||
: keyEndWithIntervalEnd(((RoutingKey)a).compareTo(((TxnIdInterval)b).end));
|
||||
}
|
||||
}
|
||||
|
||||
public CommandsForRanges(Map<? extends Timestamp, ? extends Summary> m)
|
||||
{
|
||||
super(m);
|
||||
|
|
@ -64,8 +159,10 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
{
|
||||
private final AccordCommandStore commandStore;
|
||||
private final RangeSearcher searcher;
|
||||
private AtomicReference<NavigableMap<TxnId, Ranges>> transitive = new AtomicReference<>(new TreeMap<>());
|
||||
private final ObjectHashSet<TxnId> cachedRangeTxns = new ObjectHashSet<>();
|
||||
private final AtomicReference<NavigableMap<TxnId, Ranges>> transitive = new AtomicReference<>(new TreeMap<>());
|
||||
// TODO (desired): manage memory consumed by this auxillary information
|
||||
private final Object2ObjectHashMap<TxnId, RangeRoute> cachedRangeTxnsById = new Object2ObjectHashMap<>();
|
||||
private Object[] cachedRangeTxnsByRange = IntervalBTree.empty();
|
||||
|
||||
public Manager(AccordCommandStore commandStore)
|
||||
{
|
||||
|
|
@ -78,11 +175,27 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAdd(AccordCacheEntry<TxnId, Command> state)
|
||||
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
|
||||
{
|
||||
TxnId txnId = state.key();
|
||||
if (txnId.is(Routable.Domain.Range))
|
||||
cachedRangeTxns.add(txnId);
|
||||
{
|
||||
Command cmd = state.tryGetExclusive();
|
||||
if (cmd != null)
|
||||
{
|
||||
RangeRoute upd = (RangeRoute) cmd.route();
|
||||
if (upd != null)
|
||||
{
|
||||
RangeRoute cur = cachedRangeTxnsById.put(cmd.txnId(), upd);
|
||||
if (!upd.equals(cur))
|
||||
{
|
||||
if (cur != null)
|
||||
remove(txnId, cur);
|
||||
cachedRangeTxnsByRange = IntervalBTree.update(cachedRangeTxnsByRange, toMap(txnId, upd), COMPARATORS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -90,7 +203,36 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
{
|
||||
TxnId txnId = state.key();
|
||||
if (txnId.is(Routable.Domain.Range))
|
||||
cachedRangeTxns.remove(txnId);
|
||||
{
|
||||
RangeRoute cur = cachedRangeTxnsById.remove(txnId);
|
||||
if (cur != null)
|
||||
remove(txnId, cur);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(TxnId txnId, RangeRoute route)
|
||||
{
|
||||
cachedRangeTxnsByRange = IntervalBTree.subtract(cachedRangeTxnsByRange, toMap(txnId, route), COMPARATORS);
|
||||
}
|
||||
|
||||
static Object[] toMap(TxnId txnId, RangeRoute route)
|
||||
{
|
||||
int size = route.size();
|
||||
switch (size)
|
||||
{
|
||||
case 0: return IntervalBTree.empty();
|
||||
case 1: return IntervalBTree.singleton(new TxnIdInterval(route.get(0), txnId));
|
||||
default:
|
||||
{
|
||||
try (IntervalBTree.FastInteralTreeBuilder<TxnIdInterval> builder = IntervalBTree.fastBuilder(COMPARATORS.endWithEndComparator()))
|
||||
{
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
builder.add(new TxnIdInterval(route.get(i), txnId));
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CommandsForRanges.Loader loader(@Nullable TxnId primaryTxnId, KeyHistory keyHistory, Unseekables<?> keysOrRanges)
|
||||
|
|
@ -99,9 +241,12 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
return Loader.loader(redundantBefore, primaryTxnId, keyHistory, keysOrRanges, this::newLoader);
|
||||
}
|
||||
|
||||
private Loader newLoader(Unseekables<?> searchKeysOrRanges, RedundantBefore redundantBefore, Kinds testKind, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep)
|
||||
private Loader newLoader(@Nullable TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, RedundantBefore redundantBefore, Kinds testKind, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep)
|
||||
{
|
||||
return new Loader(this, searchKeysOrRanges, redundantBefore, testKind, minTxnId, maxTxnId, findAsDep);
|
||||
MaxDecidedRX maxDecidedRX = null;
|
||||
if (primaryTxnId != null && primaryTxnId.is(Txn.Kind.ExclusiveSyncPoint) && findAsDep == null)
|
||||
maxDecidedRX = commandStore.unsafeGetMaxDecidedRX();
|
||||
return new Loader(this, searchKeysOrRanges, redundantBefore, testKind, minTxnId, maxTxnId, findAsDep, maxDecidedRX);
|
||||
}
|
||||
|
||||
private void updateTransitive(UnaryOperator<NavigableMap<TxnId, Ranges>> update)
|
||||
|
|
@ -150,15 +295,20 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
public static class Loader extends Summary.Loader
|
||||
{
|
||||
private final Manager manager;
|
||||
private final MaxDecidedRX maxDecidedRX;
|
||||
private final TxnId minRelevantId;
|
||||
|
||||
public Loader(Manager manager, Unseekables<?> searchKeysOrRanges, RedundantBefore redundantBefore, Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep)
|
||||
public Loader(Manager manager, Unseekables<?> searchKeysOrRanges, RedundantBefore redundantBefore, Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep, MaxDecidedRX maxDecidedRX)
|
||||
{
|
||||
super(searchKeysOrRanges, redundantBefore, testKinds, minTxnId, maxTxnId, findAsDep);
|
||||
super(null, searchKeysOrRanges, redundantBefore, testKinds, minTxnId, maxTxnId, findAsDep);
|
||||
this.manager = manager;
|
||||
this.maxDecidedRX = maxDecidedRX;
|
||||
this.minRelevantId = maxDecidedRX == null ? null : TxnId.nonNullOrMax(TxnId.NONE, maxDecidedRX.foldl(searchKeysOrRanges, TxnId::nonNullOrMin, null));
|
||||
}
|
||||
|
||||
public void intersects(Consumer<TxnId> forEach)
|
||||
{
|
||||
// TODO (expected): use the ranges we find to filter results by MaxDecidedRX (don't just consume the TxnId)
|
||||
switch (searchKeysOrRanges.domain())
|
||||
{
|
||||
case Range:
|
||||
|
|
@ -173,27 +323,76 @@ public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements Co
|
|||
NavigableMap<TxnId, Ranges> transitive = manager.transitive.get();
|
||||
if (!transitive.isEmpty())
|
||||
{
|
||||
for (Map.Entry<TxnId, Ranges> e : transitive.tailMap(minTxnId, true).entrySet())
|
||||
{
|
||||
if (e.getValue().intersects(searchKeysOrRanges))
|
||||
forEach.accept(e.getKey());
|
||||
}
|
||||
for (Map.Entry<TxnId, Ranges> e : transitive.tailMap(minTxnId, true).entrySet())
|
||||
{
|
||||
if (e.getValue().intersects(searchKeysOrRanges))
|
||||
forEach.accept(e.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forEachInCache(Consumer<Summary> forEach, AccordCommandStore.Caches caches)
|
||||
boolean isRelevant(TxnIdInterval txnIdInterval)
|
||||
{
|
||||
for (TxnId txnId : manager.cachedRangeTxns)
|
||||
if (maxDecidedRX == null)
|
||||
return true;
|
||||
if (txnIdInterval.txnId.compareTo(minRelevantId) < 0)
|
||||
return false;
|
||||
return maxDecidedRX.foldl(txnIdInterval.start, txnIdInterval.end, (decided, anyUndecided, test, ignore) -> test.compareTo(decided) >= 0, false, txnIdInterval.txnId, null);
|
||||
}
|
||||
|
||||
boolean isMaybeRelevant(TxnId txnId)
|
||||
{
|
||||
return maxDecidedRX == null || txnId.compareTo(minRelevantId) >= 0;
|
||||
}
|
||||
|
||||
public void forEachInCache(Unseekables<?> keysOrRanges, Consumer<Summary> forEach, AccordCommandStore.Caches caches)
|
||||
{
|
||||
switch (keysOrRanges.domain())
|
||||
{
|
||||
AccordCacheEntry<TxnId, Command> state = caches.commands().getUnsafe(txnId);
|
||||
Summary summary = ifRelevant(state);
|
||||
if (summary != null)
|
||||
forEach.accept(summary);
|
||||
default: throw new UnhandledEnum(keysOrRanges.domain());
|
||||
case Key:
|
||||
{
|
||||
for (RoutingKey key : (AbstractUnseekableKeys)keysOrRanges)
|
||||
{
|
||||
IntervalBTree.accumulate(manager.cachedRangeTxnsByRange, KEY_COMPARATORS, key, (f, s, i, c) -> {
|
||||
TxnIdInterval interval = (TxnIdInterval)i;
|
||||
if (isRelevant(interval))
|
||||
{
|
||||
TxnId txnId = ((TxnIdInterval)i).txnId;
|
||||
Summary summary = ifRelevant(c.getUnsafe(txnId));
|
||||
if (summary != null)
|
||||
f.accept(summary);
|
||||
}
|
||||
return c;
|
||||
}, forEach, this, caches.commands());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Range:
|
||||
{
|
||||
for (Range range : (AbstractRanges)keysOrRanges)
|
||||
{
|
||||
IntervalBTree.accumulate(manager.cachedRangeTxnsByRange, COMPARATORS, new TxnIdInterval(range.start(), range.end(), TxnId.NONE), (f, s, i, c) -> {
|
||||
if (isRelevant(i))
|
||||
{
|
||||
TxnId txnId = i.txnId;
|
||||
Summary summary = ifRelevant(c.getUnsafe(txnId));
|
||||
if (summary != null)
|
||||
f.accept(summary);
|
||||
}
|
||||
return c;
|
||||
}, forEach, this, caches.commands());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Summary load(TxnId txnId)
|
||||
{
|
||||
if (!isMaybeRelevant(txnId))
|
||||
return null;
|
||||
|
||||
if (findAsDep == null)
|
||||
{
|
||||
Command.Minimal cmd = manager.commandStore.loadMinimal(txnId);
|
||||
|
|
|
|||
|
|
@ -208,12 +208,6 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
listeners.forEach((i, v) -> v.cancel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TxnId primaryTxnId()
|
||||
{
|
||||
return txnId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unseekables<?> keys()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.utils;
|
||||
|
||||
public interface PeekingSearchIterator<K, V> extends SearchIterator<K, V>
|
||||
{
|
||||
/**
|
||||
* @return true if iterator has any elements left, false otherwise
|
||||
*/
|
||||
boolean hasNext();
|
||||
|
||||
V next();
|
||||
|
||||
V peek();
|
||||
}
|
||||
|
|
@ -32,8 +32,12 @@ import com.google.common.collect.Ordering;
|
|||
import accord.utils.Invariants;
|
||||
import org.apache.cassandra.utils.BiLongAccumulator;
|
||||
import org.apache.cassandra.utils.BulkIterator;
|
||||
import org.apache.cassandra.utils.IndexedSearchIterator;
|
||||
import org.apache.cassandra.utils.LongAccumulator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.PeekingSearchIterator;
|
||||
import org.apache.cassandra.utils.SearchIterator;
|
||||
import org.apache.cassandra.utils.btree.IntervalBTree.IntervalMaxIndex;
|
||||
import org.apache.cassandra.utils.caching.TinyThreadLocalPool;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
|
@ -66,7 +70,7 @@ public class BTree
|
|||
*/
|
||||
public static final int BRANCH_SHIFT = BTREE_BRANCH_SHIFT.getInt();
|
||||
|
||||
private static final int BRANCH_FACTOR = 1 << BRANCH_SHIFT;
|
||||
static final int BRANCH_FACTOR = 1 << BRANCH_SHIFT;
|
||||
public static final int MIN_KEYS = BRANCH_FACTOR / 2 - 1;
|
||||
public static final int MAX_KEYS = BRANCH_FACTOR - 1;
|
||||
public static final long STOP_SENTINEL_VALUE = Long.MAX_VALUE;
|
||||
|
|
@ -559,6 +563,7 @@ public class BTree
|
|||
for (int i = keyCount; i <= keyCount * 2; ++i)
|
||||
reverseInSitu((Object[]) tree[i], height - 1, copySizeMaps);
|
||||
|
||||
// TODO (expected): support IntervalMaxIndex
|
||||
int[] sizeMap = (int[]) tree[2 * keyCount + 1];
|
||||
if (sizeMap != DENSE_SIZE_MAPS[height - 2]) // no need to reverse a dense map; same in both directions
|
||||
{
|
||||
|
|
@ -701,7 +706,7 @@ public class BTree
|
|||
|
||||
while (!isLeaf(tree))
|
||||
{
|
||||
final int[] sizeMap = getSizeMap(tree);
|
||||
final int[] sizeMap = sizeMap(tree);
|
||||
int boundary = Arrays.binarySearch(sizeMap, index);
|
||||
if (boundary >= 0)
|
||||
{
|
||||
|
|
@ -769,7 +774,7 @@ public class BTree
|
|||
if (!exact)
|
||||
i = -1 - i;
|
||||
|
||||
int[] sizeMap = getSizeMap(node);
|
||||
int[] sizeMap = sizeMap(node);
|
||||
if (exact)
|
||||
return lb + sizeMap[i];
|
||||
else if (i > 0)
|
||||
|
|
@ -798,7 +803,7 @@ public class BTree
|
|||
return (V) node[index];
|
||||
}
|
||||
|
||||
int[] sizeMap = getSizeMap(node);
|
||||
int[] sizeMap = sizeMap(node);
|
||||
int boundary = Arrays.binarySearch(sizeMap, index);
|
||||
if (boundary >= 0)
|
||||
{
|
||||
|
|
@ -931,20 +936,12 @@ public class BTree
|
|||
return branchNode.length / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size map for the branch node
|
||||
*/
|
||||
static int[] getSizeMap(Object[] branchNode)
|
||||
{
|
||||
return (int[]) branchNode[getChildEnd(branchNode)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size map for the branch node
|
||||
*/
|
||||
static int lookupSizeMap(Object[] branchNode, int index)
|
||||
{
|
||||
return getSizeMap(branchNode)[index];
|
||||
return sizeMap(branchNode)[index];
|
||||
}
|
||||
|
||||
// get the size from the btree's index (fails if not present)
|
||||
|
|
@ -956,7 +953,7 @@ public class BTree
|
|||
// length - 1 == getChildEnd == getPositionOfSizeMap
|
||||
// (length / 2) - 1 == getChildCount - 1 == position of full tree size
|
||||
// hard code this, as will be used often;
|
||||
return ((int[]) tree[length - 1])[(length / 2) - 1];
|
||||
return sizeMap(tree)[(length / 2) - 1];
|
||||
}
|
||||
|
||||
public static long sizeOfStructureOnHeap(Object[] tree)
|
||||
|
|
@ -1378,7 +1375,7 @@ public class BTree
|
|||
appendBranchOrLeaf(builder, (Object[]) branch[i]);
|
||||
}
|
||||
// add sizeMap
|
||||
builder.append(", ").append(Arrays.toString((int[]) branch[branch.length - 1]));
|
||||
builder.append(", ").append(Arrays.toString(sizeMap(branch)));
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
@ -1400,7 +1397,7 @@ public class BTree
|
|||
{
|
||||
if (isLeaf(root))
|
||||
return keyIndex;
|
||||
int[] sizeMap = getSizeMap(root);
|
||||
int[] sizeMap = sizeMap(root);
|
||||
if ((keyIndex >= 0) & (keyIndex < sizeMap.length))
|
||||
return sizeMap[keyIndex];
|
||||
// we support asking for -1 or size, so that we can easily use this for iterator bounds checking
|
||||
|
|
@ -2191,7 +2188,7 @@ public class BTree
|
|||
// length - 1 == getChildEnd == getPositionOfSizeMap
|
||||
// (length / 2) - 1 == getChildCount - 1 == position of full tree size
|
||||
// hard code this, as will be used often;
|
||||
return ((int[]) branch[length - 1])[(length / 2) - 1];
|
||||
return sizeMap(branch)[(length / 2) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2207,7 +2204,11 @@ public class BTree
|
|||
*/
|
||||
static int[] sizeMap(Object[] branch)
|
||||
{
|
||||
return (int[]) branch[branch.length - 1];
|
||||
Object map = branch[branch.length - 1];
|
||||
if (map.getClass() == int[].class)
|
||||
return (int[])map;
|
||||
|
||||
return ((IntervalMaxIndex)map).sizeMap;
|
||||
}
|
||||
|
||||
public static long sizeOnHeapOf(Object[] tree)
|
||||
|
|
@ -2334,7 +2335,7 @@ public class BTree
|
|||
* Base class for AbstractFastBuilder.BranchBuilder, LeafBuilder and AbstractFastBuilder,
|
||||
* containing shared behaviour and declaring some useful abstract methods.
|
||||
*/
|
||||
private static abstract class LeafOrBranchBuilder
|
||||
static abstract class LeafOrBranchBuilder
|
||||
{
|
||||
final int height;
|
||||
final LeafOrBranchBuilder child;
|
||||
|
|
@ -2471,12 +2472,21 @@ public class BTree
|
|||
*/
|
||||
final BranchBuilder ensureParent()
|
||||
{
|
||||
if (parent == null)
|
||||
parent = new BranchBuilder(this);
|
||||
parent.inUse = true;
|
||||
if (parent == null) parent = allocateParent();
|
||||
else if (!parent.inUse) initParent();
|
||||
return parent;
|
||||
}
|
||||
|
||||
BranchBuilder allocateParent()
|
||||
{
|
||||
return new BranchBuilder(this);
|
||||
}
|
||||
|
||||
void initParent()
|
||||
{
|
||||
parent.inUse = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a branch builder as utilised, so that we must clear it when resetting any {@link AbstractFastBuilder}
|
||||
*
|
||||
|
|
@ -2959,7 +2969,8 @@ public class BTree
|
|||
// assumes sizes != null, since only makes sense to use this method in that context
|
||||
|
||||
int predKeys = shallowSizeOfBranch(pred);
|
||||
int[] sizeMap = (int[]) pred[2 * predKeys + 1];
|
||||
// TODO (desired): handle/copy IntervalMaxIndex
|
||||
int[] sizeMap = sizeMap(pred);
|
||||
int newKeys = 1 + predKeys;
|
||||
if (newKeys + count <= MAX_KEYS)
|
||||
{
|
||||
|
|
@ -3252,6 +3263,13 @@ public class BTree
|
|||
for (int i = 0; i < count; ++i)
|
||||
out[outOffset + i] = in[inOffset + i] - (1 + in[inOffset + i - 1]);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
Arrays.fill(buffer, null);
|
||||
count = 0;
|
||||
inUse = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3293,7 +3311,7 @@ public class BTree
|
|||
public static class FastBuilder<V> extends AbstractFastBuilder implements AutoCloseable
|
||||
{
|
||||
private static final TinyThreadLocalPool<FastBuilder<?>> POOL = new TinyThreadLocalPool<>();
|
||||
private TinyThreadLocalPool.TinyPool<FastBuilder<?>> pool;
|
||||
TinyThreadLocalPool.TinyPool pool;
|
||||
|
||||
FastBuilder()
|
||||
{
|
||||
|
|
@ -3341,9 +3359,7 @@ public class BTree
|
|||
BranchBuilder branch = leaf().parent;
|
||||
while (branch != null && branch.inUse)
|
||||
{
|
||||
Arrays.fill(branch.buffer, null);
|
||||
branch.count = 0;
|
||||
branch.inUse = false;
|
||||
branch.reset();
|
||||
branch = branch.parent;
|
||||
}
|
||||
}
|
||||
|
|
@ -3442,10 +3458,10 @@ public class BTree
|
|||
* Searches within both trees to accelerate the process of modification, instead of performing a simple
|
||||
* iteration over the new tree.
|
||||
*/
|
||||
private static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractUpdater implements AutoCloseable
|
||||
static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractUpdater implements AutoCloseable
|
||||
{
|
||||
static final TinyThreadLocalPool<Updater> POOL = new TinyThreadLocalPool<>();
|
||||
TinyThreadLocalPool.TinyPool<Updater> pool;
|
||||
TinyThreadLocalPool.TinyPool pool;
|
||||
|
||||
// the new tree we navigate linearly, and are always on a key or at the end
|
||||
final SimpleTreeKeysIterator<Compare, Insert> insert = new SimpleTreeKeysIterator<>();
|
||||
|
|
@ -3453,7 +3469,6 @@ public class BTree
|
|||
Comparator<? super Compare> comparator;
|
||||
UpdateFunction<Insert, Existing> updateF;
|
||||
|
||||
|
||||
static <Compare, Existing extends Compare, Insert extends Compare> Updater<Compare, Existing, Insert> get()
|
||||
{
|
||||
TinyThreadLocalPool.TinyPool<Updater> pool = POOL.get();
|
||||
|
|
@ -3626,6 +3641,7 @@ public class BTree
|
|||
reset();
|
||||
pool.offer(this);
|
||||
pool = null;
|
||||
comparator = null;
|
||||
}
|
||||
|
||||
void reset()
|
||||
|
|
@ -3655,7 +3671,7 @@ public class BTree
|
|||
* <p>
|
||||
* The approach taken here hopefully balances simplicity, garbage generation and execution time.
|
||||
*/
|
||||
private static abstract class AbstractTransformer<I, O> extends AbstractUpdater implements AutoCloseable
|
||||
static abstract class AbstractTransformer<I, O> extends AbstractUpdater implements AutoCloseable
|
||||
{
|
||||
/**
|
||||
* An iterator over the tree we are updating
|
||||
|
|
@ -3840,7 +3856,7 @@ public class BTree
|
|||
* we refuse to construct a leaf and return null. Otherwise we propagate the branch to its parent's buffer
|
||||
* and return the branch we have constructed.
|
||||
*/
|
||||
private boolean finish(LeafOrBranchBuilder level, Object[] unode)
|
||||
final boolean finish(LeafOrBranchBuilder level, Object[] unode)
|
||||
{
|
||||
if (!level.isSufficient())
|
||||
return false;
|
||||
|
|
@ -3857,7 +3873,7 @@ public class BTree
|
|||
* does not, we recursively apply the stealing procedure to obtain a non-empty parent. If this process manages
|
||||
* to reach the root and still find no preceding branch, this will result in making this branch the new root.
|
||||
*/
|
||||
private Object[] finishAndDrain(boolean skipLeaf)
|
||||
final Object[] finishAndDrain(boolean skipLeaf)
|
||||
{
|
||||
LeafOrBranchBuilder level = leaf();
|
||||
if (skipLeaf)
|
||||
|
|
@ -3974,6 +3990,191 @@ public class BTree
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement set subtraction/difference using a modified version of the Transformer logic
|
||||
*/
|
||||
static abstract class Subtraction<K, T extends K> extends AbstractTransformer<T, T> implements AutoCloseable
|
||||
{
|
||||
/**
|
||||
* An iterator over the tree we are updating
|
||||
*/
|
||||
PeekingSearchIterator<K, ? extends K> remove;
|
||||
Comparator<K> comparator;
|
||||
|
||||
Object[] apply(Object[] update, PeekingSearchIterator<K, ? extends K> remove)
|
||||
{
|
||||
int height = this.update.init(update);
|
||||
this.remove = remove;
|
||||
if (queuedToFinish.length < height - 1)
|
||||
queuedToFinish = new Object[height - 1][];
|
||||
return apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
T apply(T v)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* We base our operation on the shape of {@code update}, trying to steal as much of the original tree as
|
||||
* possible for our new tree
|
||||
*/
|
||||
private Object[] apply()
|
||||
{
|
||||
Object[] unode = update.node();
|
||||
int upos = update.position(), usz = sizeOfLeaf(unode);
|
||||
while (true)
|
||||
{
|
||||
// we always start the loop on a leaf node, for both input and output
|
||||
boolean propagatedOriginalLeaf = false;
|
||||
if (leaf().count == 0 && upos == 0)
|
||||
{
|
||||
int prev = 0;
|
||||
while (upos < usz)
|
||||
{
|
||||
// fast path - buffer is empty and input unconsumed, so may be able to propagate original
|
||||
if (null == remove.next((T)unode[upos]))
|
||||
{
|
||||
if (!remove.hasNext())
|
||||
break;
|
||||
upos = exponentialSearch(comparator, unode, upos + 1, usz, remove.peek());
|
||||
if (upos < 0)
|
||||
{
|
||||
upos = -1 - upos;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
leaf().copyNoOverflow(unode, prev, upos - prev);
|
||||
++upos;
|
||||
prev = upos;
|
||||
}
|
||||
if (propagatedOriginalLeaf = (prev == 0))
|
||||
{
|
||||
// if input is unmodified by transformation, propagate the input node
|
||||
markUsed(parent).addChild(unode, usz);
|
||||
}
|
||||
else
|
||||
{
|
||||
leaf().copyNoOverflow(unode, prev, usz - prev);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int prev = upos;
|
||||
while (upos < usz)
|
||||
{
|
||||
if (null == remove.next((T)unode[upos]))
|
||||
{
|
||||
if (!remove.hasNext())
|
||||
break;
|
||||
upos = exponentialSearch(comparator, unode, upos + 1, usz, remove.peek());
|
||||
if (upos < 0)
|
||||
{
|
||||
upos = -1 - upos;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
leaf().copy(unode, prev, upos - prev);
|
||||
++upos;
|
||||
prev = upos;
|
||||
}
|
||||
leaf().copy(unode, prev, usz - prev);
|
||||
}
|
||||
|
||||
// we've finished a leaf, and have to hand it to a parent alongside its right-hand key
|
||||
// so now we try to do two things:
|
||||
// 1) find the next unfiltered key from our unfinished parent
|
||||
// 2) determine how many parents are "finished" and whose buffers we should also attempt to propagate
|
||||
// we do (1) unconditionally, because:
|
||||
// a) we need to handle the branch keys somewhere, and it may as well happen in one place
|
||||
// b) we either need more keys for our incomplete leaf; or
|
||||
// c) we need a key to go after our last propagated node in any unfinished parent
|
||||
|
||||
int finishToHeight = 0;
|
||||
T next;
|
||||
do
|
||||
{
|
||||
if (!update.ascendToParent())
|
||||
return finishAndDrain(propagatedOriginalLeaf);
|
||||
|
||||
BranchBuilder level = parent;
|
||||
unode = update.node();
|
||||
upos = update.position();
|
||||
usz = shallowSizeOfBranch(unode);
|
||||
|
||||
while (upos == usz)
|
||||
{
|
||||
queuedToFinish[level.height - 2] = unode;
|
||||
finishToHeight = max(finishToHeight, level.height);
|
||||
|
||||
if (!update.ascendToParent())
|
||||
return finishAndDrain(propagatedOriginalLeaf);
|
||||
|
||||
level = level.ensureParent();
|
||||
unode = update.node();
|
||||
upos = update.position();
|
||||
usz = shallowSizeOfBranch(unode);
|
||||
}
|
||||
|
||||
next = (T) unode[upos];
|
||||
if (null != remove.next(next))
|
||||
next = null;
|
||||
|
||||
update.descendIntoNextLeaf(unode, upos, usz);
|
||||
unode = update.node();
|
||||
upos = update.position();
|
||||
usz = sizeOfLeaf(unode);
|
||||
|
||||
// nextKey might have been filtered, so we may need to look in this next leaf for it
|
||||
while (next == null && upos < usz)
|
||||
{
|
||||
next = (T)unode[upos++];
|
||||
if (null != remove.next(next))
|
||||
next = null;
|
||||
}
|
||||
|
||||
// if we still found no key loop and try again on the next parent, leaf, parent... ad infinitum
|
||||
} while (next == null);
|
||||
|
||||
// we always end with unode a leaf, though it may be that upos == usz and that we will do nothing with it
|
||||
|
||||
// we've found a non-null key, now decide what to do with it:
|
||||
// 1) if we have insufficient keys in our leaf, simply append to the leaf and continue;
|
||||
// 2) otherwise, walk our parent branches finishing those *before* {@code finishTo}
|
||||
// 2a) if any cannot be finished, append our new key to it and stop finishing further parents; they
|
||||
// will be finished the next time we ascend to their level with a complete chain of finishable branches
|
||||
// 2b) otherwise, add our new key to {@code finishTo}
|
||||
|
||||
if (!propagatedOriginalLeaf && !finish(leaf(), null))
|
||||
{
|
||||
leaf().addKeyNoOverflow(next);
|
||||
continue;
|
||||
}
|
||||
|
||||
BranchBuilder finish = parent;
|
||||
while (true)
|
||||
{
|
||||
if (finish.height <= finishToHeight)
|
||||
{
|
||||
Object[] originalNode = queuedToFinish[finish.height - 2];
|
||||
if (finish(finish, originalNode))
|
||||
{
|
||||
finish = finish.parent;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// add our key to the last unpropagated parent branch buffer
|
||||
finish.addKey(next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Transformer<I, O> extends AbstractTransformer<I, O>
|
||||
{
|
||||
|
|
@ -4131,7 +4332,6 @@ public class BTree
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleTreeKeysIterator<Compare, Insert extends Compare>
|
||||
{
|
||||
int leafSize;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ package org.apache.cassandra.utils.btree;
|
|||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import static org.apache.cassandra.utils.btree.BTree.sizeMap;
|
||||
|
||||
public class BTreeRemoval
|
||||
{
|
||||
/**
|
||||
|
|
@ -45,7 +47,7 @@ public class BTreeRemoval
|
|||
index = lb + i;
|
||||
else
|
||||
{
|
||||
final int indexInNode = BTree.getSizeMap(node)[i];
|
||||
final int indexInNode = sizeMap(node)[i];
|
||||
index = lb + indexInNode - 1;
|
||||
elemToSwap = BTree.findByIndex(node, indexInNode - 1);
|
||||
}
|
||||
|
|
@ -56,7 +58,7 @@ public class BTreeRemoval
|
|||
|
||||
i = -1 - i;
|
||||
if (i > 0)
|
||||
lb += BTree.getSizeMap(node)[i - 1] + 1;
|
||||
lb += sizeMap(node)[i - 1] + 1;
|
||||
|
||||
node = (Object[]) node[keyEnd + i];
|
||||
}
|
||||
|
|
@ -80,9 +82,9 @@ public class BTreeRemoval
|
|||
while (!BTree.isLeaf(node))
|
||||
{
|
||||
final int keyEnd = BTree.getBranchKeyEnd(node);
|
||||
int i = -1 - Arrays.binarySearch(BTree.getSizeMap(node), index);
|
||||
int i = -1 - Arrays.binarySearch(sizeMap(node), index);
|
||||
if (i > 0)
|
||||
index -= (1 + BTree.getSizeMap(node)[i - 1]);
|
||||
index -= (1 + sizeMap(node)[i - 1]);
|
||||
Object[] nextNode = (Object[]) node[keyEnd + i];
|
||||
boolean nextNodeNeedsCopy = true;
|
||||
if (BTree.getKeyEnd(nextNode) > BTree.MIN_KEYS)
|
||||
|
|
@ -124,7 +126,7 @@ public class BTreeRemoval
|
|||
|
||||
if (node != null)
|
||||
{
|
||||
final int[] sizeMap = BTree.getSizeMap(node);
|
||||
final int[] sizeMap = sizeMap(node);
|
||||
for (int j = i; j < sizeMap.length; ++j)
|
||||
sizeMap[j] -= 1;
|
||||
if (prevNode != null)
|
||||
|
|
@ -160,7 +162,7 @@ public class BTreeRemoval
|
|||
copyWithKeyAndChildInserted(nextNode, nextKeyEnd, node[i], BTree.getChildCount(nextNode), newChild);
|
||||
node[i] = rightNeighbour[0];
|
||||
node[keyEnd + i + 1] = copyWithKeyAndChildRemoved(rightNeighbour, 0, 0, true);
|
||||
BTree.getSizeMap(node)[i] +=
|
||||
sizeMap(node)[i] +=
|
||||
leaves ? 1 : 1 + BTree.size((Object[]) newNextNode[BTree.getChildEnd(newNextNode) - 1]);
|
||||
return newNextNode;
|
||||
}
|
||||
|
|
@ -176,7 +178,7 @@ public class BTreeRemoval
|
|||
final Object[] newNextNode = copyWithKeyAndChildInserted(nextNode, 0, node[i - 1], 0, newChild);
|
||||
node[i - 1] = leftNeighbour[leftNeighbourEndKey - 1];
|
||||
node[keyEnd + i - 1] = copyWithKeyAndChildRemoved(leftNeighbour, leftNeighbourEndKey - 1, leftNeighbourEndKey, true);
|
||||
BTree.getSizeMap(node)[i - 1] -= leaves ? 1 : 1 + BTree.getSizeMap(newNextNode)[0];
|
||||
sizeMap(node)[i - 1] -= leaves ? 1 : 1 + sizeMap(newNextNode)[0];
|
||||
return newNextNode;
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +213,7 @@ public class BTreeRemoval
|
|||
copy,
|
||||
keyEnd + childIndex + 2,
|
||||
keyEnd - childIndex + 1);
|
||||
final int[] sizeMap = BTree.getSizeMap(node);
|
||||
final int[] sizeMap = sizeMap(node);
|
||||
final int[] newSizeMap = new int[sizeMap.length + 1];
|
||||
if (childIndex > 0)
|
||||
System.arraycopy(sizeMap, 0, newSizeMap, 0, childIndex);
|
||||
|
|
@ -241,7 +243,7 @@ public class BTreeRemoval
|
|||
if (!leaf)
|
||||
{
|
||||
offset = copyChildren(node, newNode, offset, childIndex);
|
||||
final int[] nodeSizeMap = BTree.getSizeMap(node);
|
||||
final int[] nodeSizeMap = sizeMap(node);
|
||||
final int[] newNodeSizeMap = new int[nodeSizeMap.length - 1];
|
||||
int pos = 0;
|
||||
final int sizeToRemove = BTree.size((Object[])node[BTree.getChildStart(node) + childIndex]) + 1;
|
||||
|
|
@ -272,8 +274,8 @@ public class BTreeRemoval
|
|||
{
|
||||
offset = copyChildren(left, result, offset);
|
||||
offset = copyChildren(right, result, offset);
|
||||
final int[] leftSizeMap = BTree.getSizeMap(left);
|
||||
final int[] rightSizeMap = BTree.getSizeMap(right);
|
||||
final int[] leftSizeMap = sizeMap(left);
|
||||
final int[] rightSizeMap = sizeMap(right);
|
||||
final int[] newSizeMap = new int[leftSizeMap.length + rightSizeMap.length];
|
||||
offset = 0;
|
||||
offset = copySizeMap(leftSizeMap, newSizeMap, offset, 0);
|
||||
|
|
@ -335,7 +337,7 @@ public class BTreeRemoval
|
|||
System.arraycopy(node, 0, copy, 0, node.length);
|
||||
if (!BTree.isLeaf(node))
|
||||
{
|
||||
final int[] sizeMap = BTree.getSizeMap(node);
|
||||
final int[] sizeMap = sizeMap(node);
|
||||
final int[] copySizeMap = new int[sizeMap.length];
|
||||
System.arraycopy(sizeMap, 0, copySizeMap, 0, sizeMap.length);
|
||||
copy[copy.length - 1] = copySizeMap;
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ package org.apache.cassandra.utils.btree;
|
|||
import java.util.Iterator;
|
||||
|
||||
import org.apache.cassandra.utils.IndexedSearchIterator;
|
||||
import org.apache.cassandra.utils.PeekingSearchIterator;
|
||||
|
||||
|
||||
public interface BTreeSearchIterator<K, V> extends IndexedSearchIterator<K, V>, Iterator<V>
|
||||
public interface BTreeSearchIterator<K, V> extends IndexedSearchIterator<K, V>, Iterator<V>, PeekingSearchIterator<K, V>
|
||||
{
|
||||
/**
|
||||
* Reset this Iterator to its starting position
|
||||
|
|
|
|||
|
|
@ -79,8 +79,7 @@ public class FullBTreeSearchIterator<K, V> extends TreeCursor<K> implements BTre
|
|||
state = END;
|
||||
break;
|
||||
case BEFORE_FIRST:
|
||||
seekTo(index = forwards ? lowerBound : upperBound);
|
||||
state = (byte) (upperBound == lowerBound ? LAST : MIDDLE);
|
||||
moveStart();
|
||||
case LAST:
|
||||
case MIDDLE:
|
||||
state |= ON_ITEM;
|
||||
|
|
@ -92,6 +91,29 @@ public class FullBTreeSearchIterator<K, V> extends TreeCursor<K> implements BTre
|
|||
return current();
|
||||
}
|
||||
|
||||
private void moveStart()
|
||||
{
|
||||
seekTo(index = forwards ? lowerBound : upperBound);
|
||||
state = (byte) (upperBound == lowerBound ? LAST : MIDDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V peek()
|
||||
{
|
||||
if (state == END)
|
||||
throw new NoSuchElementException();
|
||||
|
||||
if (state == BEFORE_FIRST)
|
||||
moveStart();
|
||||
|
||||
if ((state & ON_ITEM) == 1)
|
||||
{
|
||||
state &= ~ON_ITEM;
|
||||
index = moveOne(forwards);
|
||||
}
|
||||
return (V) currentValue();
|
||||
}
|
||||
|
||||
public V next(K target)
|
||||
{
|
||||
if (!hasNext())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.utils.btree;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.QuadFunction;
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
import org.apache.cassandra.utils.caching.TinyThreadLocalPool;
|
||||
|
||||
import static org.apache.cassandra.utils.btree.BTree.BRANCH_FACTOR;
|
||||
import static org.apache.cassandra.utils.btree.BTree.Dir.ASC;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getBranchKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getChildCount;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getChildStart;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getLeafKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.BTree.isEmpty;
|
||||
import static org.apache.cassandra.utils.btree.BTree.isLeaf;
|
||||
import static org.apache.cassandra.utils.btree.BTree.size;
|
||||
import static org.apache.cassandra.utils.btree.BTree.sizeMap;
|
||||
import static org.apache.cassandra.utils.btree.BTree.slice;
|
||||
|
||||
/**
|
||||
* A very simple extension to BTree to provide an Augmented Interval BTree.
|
||||
*
|
||||
* TODO (desired): there are a number of obvious performance improvements that should be pursued at the earliest suitable opportunity:
|
||||
* - IntervalMaxIndex should include an index over any direct child leaves to bound worst case to O(lgN + m) rather than O(lgN + m.B)
|
||||
* - IntervalMaxIndex should include the immediately preceding self-key in any max key, so we can avoid the O(N) loop and simply pre-visit the self-key
|
||||
* - IntervalMaxIndex should omit entirely keys/nodes that do not extend past the following key
|
||||
* - Updater/Subtraction should re-use parts of IntervalMaxIndex where appropriate
|
||||
*/
|
||||
public class IntervalBTree
|
||||
{
|
||||
public interface IntervalComparators<V>
|
||||
{
|
||||
Comparator<V> totalOrder();
|
||||
Comparator<V> startWithStartComparator();
|
||||
Comparator<V> startWithEndComparator();
|
||||
Comparator<V> endWithStartComparator();
|
||||
Comparator<V> endWithEndComparator();
|
||||
}
|
||||
|
||||
public static class InclusiveEndKeyComparatorHelper
|
||||
{
|
||||
public static int keyStartWithIntervalStart(int c) { return equalsMeansBefore(c); }
|
||||
public static int intervalStartWithKeyStart(int c) { return equalsMeansAfter(c); }
|
||||
public static int keyStartWithIntervalEnd(int c) { return equalsMeansBefore(c); }
|
||||
public static int intervalStartWithKeyEnd(int c) { return equalsMeansAfter(c); }
|
||||
public static int keyEndWithIntervalStart(int c) { return equalsMeansAfter(c); }
|
||||
public static int intervalEndWithKeyStart(int c) { return equalsMeansAfter(c); }
|
||||
public static int intervalEndWithKeyEnd(int c) { return equalsMeansAfter(c); }
|
||||
public static int keyEndWithIntervalEnd(int c) { return equalsMeansBefore(c); }
|
||||
private static int equalsMeansAfter(int c) { return c == 0 ? 1 : c; }
|
||||
private static int equalsMeansBefore(int c) { return c == 0 ? -1 : c; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the accumulation function over all intersecting intervals in the tree
|
||||
*/
|
||||
public static <R, P1, P2, V> V accumulate(Object[] btree, IntervalComparators<R> comparators, R intersects, QuadFunction<P1, P2, R, V, V> function, P1 p1, P2 p2, V accumulate)
|
||||
{
|
||||
if (isLeaf(btree))
|
||||
{
|
||||
Comparator comparator = comparators.startWithEndComparator();
|
||||
int keyEnd = getLeafKeyEnd(btree);
|
||||
for (int i = 0; i < keyEnd; ++i)
|
||||
{
|
||||
R v = (R) btree[i];
|
||||
if (comparator.compare(v, intersects) < 0 && comparator.compare(intersects, v) < 0)
|
||||
accumulate = function.apply(p1, p2, v, accumulate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int startKey = Arrays.binarySearch(btree, 0, getKeyEnd(btree), intersects, (Comparator) comparators.startWithStartComparator());
|
||||
int startChild;
|
||||
if (startKey >= 0) startChild = startKey + 1;
|
||||
else startChild = (startKey = -1 - startKey);
|
||||
int endKey = Arrays.binarySearch(btree, startKey, getKeyEnd(btree), intersects, (Comparator) comparators.startWithEndComparator());
|
||||
int endChild;
|
||||
if (endKey >= 0) endChild = 1 + endKey;
|
||||
else endChild = 1 + (endKey = -1 - endKey);
|
||||
|
||||
{ // descend anything with a max that overlaps us that we won't already visit
|
||||
if (startChild > 0 || startKey > 0)
|
||||
accumulate = accumulateMaxOnly(startChild + (startChild == endChild ? 1 : 0), startKey, btree, comparators, intersects, function, p1, p2, accumulate);
|
||||
}
|
||||
|
||||
int childOffset = getChildStart(btree);
|
||||
if (startChild == startKey && startChild < endChild)
|
||||
accumulate = accumulate((Object[]) btree[childOffset + startChild++], comparators, intersects, function, p1, p2, accumulate);
|
||||
|
||||
if (startKey < startChild && startKey < endKey)
|
||||
accumulate = function.apply(p1, p2, (R) btree[startKey++], accumulate);
|
||||
|
||||
while (startChild < endChild - 1)
|
||||
{
|
||||
Invariants.require(startKey == startChild);
|
||||
accumulate = accumulate((Object[]) btree[childOffset + startChild++], function, p1, p2, accumulate);
|
||||
accumulate = function.apply(p1, p2, (R) btree[startKey++], accumulate);
|
||||
}
|
||||
if (startKey < endKey)
|
||||
accumulate = function.apply(p1, p2, (R) btree[startKey], accumulate);
|
||||
if (startChild < endChild)
|
||||
accumulate = accumulate((Object[]) btree[childOffset + startChild], comparators, intersects, function, p1, p2, accumulate);
|
||||
}
|
||||
return accumulate;
|
||||
}
|
||||
|
||||
public static <R, P1, P2, V> V accumulate(Object[] btree, QuadFunction<P1, P2, R, V, V> function, P1 p1, P2 p2, V accumulate)
|
||||
{
|
||||
if (isLeaf(btree))
|
||||
{
|
||||
for (int i = 0, maxi = getLeafKeyEnd(btree); i < maxi; ++i)
|
||||
accumulate = function.apply(p1, p2, (R) btree[i], accumulate);
|
||||
}
|
||||
else
|
||||
{
|
||||
int keyEnd = getBranchKeyEnd(btree);
|
||||
for (int i = 0; i < keyEnd; ++i)
|
||||
{
|
||||
accumulate = accumulate((Object[]) btree[keyEnd + i], function, p1, p2, accumulate);
|
||||
accumulate = function.apply(p1, p2, (R) btree[i], accumulate);
|
||||
}
|
||||
accumulate = accumulate((Object[]) btree[2 * keyEnd], function, p1, p2, accumulate);
|
||||
}
|
||||
return accumulate;
|
||||
}
|
||||
|
||||
private static <R, P1, P2, V> V accumulateMaxOnly(int ifChildBefore, int ifKeyBefore, Object[] btree, IntervalComparators<R> comparators, R intersects, QuadFunction<P1, P2, R, V, V> function, P1 p1, P2 p2, V accumulate)
|
||||
{
|
||||
if (isLeaf(btree))
|
||||
{
|
||||
Invariants.require(ifChildBefore == Integer.MAX_VALUE);
|
||||
Comparator comparator = comparators.startWithEndComparator();
|
||||
for (int i = 0, maxi = getLeafKeyEnd(btree); i < maxi; ++i)
|
||||
{
|
||||
R v = (R) btree[i];
|
||||
if (comparator.compare(intersects, v) < 0)
|
||||
{
|
||||
Invariants.paranoid(comparator.compare(v, intersects) < 0);
|
||||
accumulate = function.apply(p1, p2, v, accumulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int keyEnd = getChildStart(btree);
|
||||
IntervalMaxIndex intervalMaxIndex = getIntervalMaxIndex(btree);
|
||||
int descendMaxStart = Arrays.binarySearch(intervalMaxIndex.sortedByEnd, intersects, (Comparator) comparators.endWithStartComparator());
|
||||
if (descendMaxStart < 0)
|
||||
descendMaxStart = -1 - descendMaxStart;
|
||||
int[] sortedByEndIndex = intervalMaxIndex.sortedByEndIndex;
|
||||
for (int i = descendMaxStart; i < sortedByEndIndex.length; ++i)
|
||||
{
|
||||
int index = sortedByEndIndex[i];
|
||||
if (index < ifChildBefore)
|
||||
accumulate = accumulateMaxOnly(Integer.MAX_VALUE, Integer.MAX_VALUE, (Object[]) btree[keyEnd + index], comparators, intersects, function, p1, p2, accumulate);
|
||||
}
|
||||
Comparator comparator = comparators.startWithEndComparator();
|
||||
for (int i = 0, maxi = Math.min(ifKeyBefore, keyEnd); i < maxi; ++i)
|
||||
{
|
||||
R v = (R) btree[i];
|
||||
if (comparator.compare(intersects, v) < 0)
|
||||
{
|
||||
Invariants.paranoid(comparator.compare(v, intersects) < 0);
|
||||
accumulate = function.apply(p1, p2, v, accumulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return accumulate;
|
||||
}
|
||||
|
||||
static class IntervalIndexAdapter implements Comparator<IntervalIndexAdapter.SortEntry>
|
||||
{
|
||||
static class SortEntry
|
||||
{
|
||||
Object sort;
|
||||
int index;
|
||||
}
|
||||
|
||||
SortEntry[] sort = new SortEntry[BRANCH_FACTOR];
|
||||
Comparator endSorter;
|
||||
|
||||
public void override(Object[] branch)
|
||||
{
|
||||
int[] sizeMap = sizeMap(branch);
|
||||
int childStart = getChildStart(branch), childCount = getChildCount(branch);
|
||||
for (int i = 0; i < childCount; ++i)
|
||||
{
|
||||
Object[] child = (Object[]) branch[i + childStart];
|
||||
Object max = maxChild(child);
|
||||
if (sort[i] == null)
|
||||
sort[i] = new SortEntry();
|
||||
sort[i].index = i;
|
||||
sort[i].sort = max;
|
||||
}
|
||||
Arrays.sort(sort, 0, childCount, this);
|
||||
Object[] sortedByEnd = new Object[childCount];
|
||||
int[] sortedByEndIndex = new int[childCount];
|
||||
for (int i = 0; i < childCount; ++i)
|
||||
{
|
||||
sortedByEnd[i] = sort[i].sort;
|
||||
sortedByEndIndex[i] = sort[i].index;
|
||||
}
|
||||
branch[branch.length - 1] = new IntervalMaxIndex(sortedByEnd, sortedByEndIndex, sizeMap);
|
||||
}
|
||||
|
||||
private Object maxChild(Object[] child)
|
||||
{
|
||||
Object max = child[0];
|
||||
for (int j = 1, jend = getKeyEnd(child); j < jend; ++j)
|
||||
{
|
||||
if (endSorter.compare(child[j], max) > 0)
|
||||
max = child[j];
|
||||
}
|
||||
if (!isLeaf(child))
|
||||
{
|
||||
IntervalMaxIndex childIndex = getIntervalMaxIndex(child);
|
||||
Object maxChild = childIndex.sortedByEnd[childIndex.sortedByEnd.length - 1];
|
||||
if (endSorter.compare(maxChild, max) > 0)
|
||||
max = maxChild;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(SortEntry o1, SortEntry o2)
|
||||
{
|
||||
return endSorter.compare(o1.sort, o2.sort);
|
||||
}
|
||||
}
|
||||
|
||||
static class IntervalBranchBuilder extends BTree.BranchBuilder
|
||||
{
|
||||
IntervalIndexAdapter adapter;
|
||||
|
||||
IntervalBranchBuilder(BTree.LeafOrBranchBuilder child)
|
||||
{
|
||||
super(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
BTree.BranchBuilder allocateParent()
|
||||
{
|
||||
return new IntervalBranchBuilder(this).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initParent()
|
||||
{
|
||||
super.initParent();
|
||||
((IntervalBranchBuilder) parent).init(adapter);
|
||||
}
|
||||
|
||||
private IntervalBranchBuilder init(IntervalIndexAdapter adapter)
|
||||
{
|
||||
this.adapter = adapter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
int setDrainSizeMap(Object[] original, int keysInOriginal, Object[] branch, int keysInBranch)
|
||||
{
|
||||
int result = super.setDrainSizeMap(original, keysInOriginal, branch, keysInBranch);
|
||||
adapter.override(branch);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setRedistributedSizeMap(Object[] branch, int steal)
|
||||
{
|
||||
super.setRedistributedSizeMap(branch, steal);
|
||||
adapter.override(branch);
|
||||
}
|
||||
|
||||
@Override
|
||||
int setOverflowSizeMap(Object[] branch, int keys)
|
||||
{
|
||||
int result = super.setOverflowSizeMap(branch, keys);
|
||||
adapter.override(branch);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FastInteralTreeBuilder<V> extends BTree.FastBuilder<V>
|
||||
{
|
||||
private static final TinyThreadLocalPool<FastInteralTreeBuilder<?>> POOL = new TinyThreadLocalPool<>();
|
||||
final IntervalIndexAdapter adapter = new IntervalIndexAdapter();
|
||||
|
||||
@Override
|
||||
BTree.BranchBuilder allocateParent()
|
||||
{
|
||||
return new IntervalBranchBuilder(this).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initParent()
|
||||
{
|
||||
super.initParent();
|
||||
((IntervalBranchBuilder) parent).init(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
static class IntervalUpdater<Compare, Existing extends Compare, Insert extends Compare> extends BTree.Updater<Compare, Existing, Insert>
|
||||
{
|
||||
static final TinyThreadLocalPool<IntervalUpdater> POOL = new TinyThreadLocalPool<>();
|
||||
private final IntervalIndexAdapter adapter = new IntervalIndexAdapter();
|
||||
|
||||
static <Compare, Existing extends Compare, Insert extends Compare> IntervalUpdater<Compare, Existing, Insert> get(Comparator<Compare> compareEnds)
|
||||
{
|
||||
TinyThreadLocalPool.TinyPool<IntervalUpdater> pool = POOL.get();
|
||||
IntervalUpdater<Compare, Existing, Insert> updater = pool.poll();
|
||||
if (updater == null)
|
||||
updater = new IntervalUpdater<>();
|
||||
updater.pool = pool;
|
||||
updater.adapter.endSorter = compareEnds;
|
||||
return updater;
|
||||
}
|
||||
|
||||
@Override
|
||||
BTree.BranchBuilder allocateParent()
|
||||
{
|
||||
return new IntervalBranchBuilder(this).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initParent()
|
||||
{
|
||||
super.initParent();
|
||||
((IntervalBranchBuilder) parent).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
adapter.endSorter = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object[] empty()
|
||||
{
|
||||
return BTree.empty();
|
||||
}
|
||||
|
||||
public static Object[] singleton(Object value)
|
||||
{
|
||||
return BTree.singleton(value);
|
||||
}
|
||||
|
||||
static class Subtraction<K, T extends K> extends BTree.Subtraction<K, T>
|
||||
{
|
||||
static final FastThreadLocal<Subtraction> SHARED = new FastThreadLocal<>();
|
||||
private final IntervalIndexAdapter adapter = new IntervalIndexAdapter();
|
||||
|
||||
Subtraction()
|
||||
{
|
||||
((IntervalBranchBuilder)parent).adapter = adapter;
|
||||
}
|
||||
|
||||
static <K, T extends K> Subtraction<K, T> get(IntervalComparators<K> comparators)
|
||||
{
|
||||
Subtraction subtraction = SHARED.get();
|
||||
if (subtraction == null)
|
||||
SHARED.set(subtraction = new Subtraction());
|
||||
subtraction.comparator = comparators.totalOrder();
|
||||
subtraction.adapter.endSorter = comparators.endWithEndComparator();
|
||||
return subtraction;
|
||||
}
|
||||
|
||||
@Override
|
||||
BTree.BranchBuilder allocateParent()
|
||||
{
|
||||
return new IntervalBranchBuilder(this).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initParent()
|
||||
{
|
||||
super.initParent();
|
||||
((IntervalBranchBuilder) parent).init(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
reset();
|
||||
comparator = null;
|
||||
adapter.endSorter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts {@code insert} into {@code update}, applying {@code updateF} to each new item in {@code insert},
|
||||
* as well as any matched items in {@code update}.
|
||||
* <p>
|
||||
* Note that {@code UpdateFunction.noOp} is assumed to indicate a lack of interest in which value survives.
|
||||
*/
|
||||
public static <Compare> Object[] subtract(Object[] toUpdate, Object[] subtract, IntervalComparators<Compare> comparators)
|
||||
{
|
||||
try (Subtraction subtraction = IntervalBTree.Subtraction.get(comparators))
|
||||
{
|
||||
return subtraction.apply(toUpdate, slice(subtract, comparators.totalOrder(), ASC));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts {@code insert} into {@code update}, applying {@code updateF} to each new item in {@code insert},
|
||||
* as well as any matched items in {@code update}.
|
||||
* <p>
|
||||
* Note that {@code UpdateFunction.noOp} is assumed to indicate a lack of interest in which value survives.
|
||||
*/
|
||||
public static <Compare, Existing extends Compare, Insert extends Compare> Object[] update(Object[] existing,
|
||||
Object[] insert,
|
||||
IntervalComparators<Compare> comparators)
|
||||
{
|
||||
// perform some initial obvious optimisations
|
||||
if (isEmpty(insert))
|
||||
return existing; // do nothing if update is empty
|
||||
|
||||
if (isEmpty(existing))
|
||||
return insert;
|
||||
|
||||
if (isLeaf(insert))
|
||||
{
|
||||
// consider flipping the order of application, if update is much larger than insert and applying unary no-op
|
||||
int updateSize = size(existing);
|
||||
int insertSize = size(insert);
|
||||
int scale = Integer.numberOfLeadingZeros(updateSize) - Integer.numberOfLeadingZeros(insertSize);
|
||||
if (scale >= 4)
|
||||
{
|
||||
// i.e. at roughly 16x the size, or one tier deeper - very arbitrary, should pick more carefully
|
||||
// experimentally, at least at 64x the size the difference in performance is ~10x
|
||||
Object[] tmp = insert;
|
||||
insert = existing;
|
||||
existing = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
try (IntervalUpdater<Compare, Existing, Insert> updater = IntervalUpdater.get(comparators.endWithEndComparator()))
|
||||
{
|
||||
return updater.update(existing, insert, comparators.totalOrder(), (UpdateFunction) UpdateFunction.noOp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a tree of unknown size, in order.
|
||||
*/
|
||||
public static <V> FastInteralTreeBuilder<V> fastBuilder(Comparator<V> endSorter)
|
||||
{
|
||||
TinyThreadLocalPool.TinyPool<FastInteralTreeBuilder<?>> pool = FastInteralTreeBuilder.POOL.get();
|
||||
FastInteralTreeBuilder<V> builder = (FastInteralTreeBuilder<V>) pool.poll();
|
||||
if (builder == null)
|
||||
builder = new FastInteralTreeBuilder<>();
|
||||
builder.pool = pool;
|
||||
builder.adapter.endSorter = endSorter;
|
||||
return builder;
|
||||
}
|
||||
|
||||
// TODO (desired): index leaf nodes
|
||||
static class IntervalMaxIndex
|
||||
{
|
||||
final Object[] sortedByEnd;
|
||||
final int[] sortedByEndIndex;
|
||||
final int[] sizeMap;
|
||||
|
||||
IntervalMaxIndex(Object[] sortedByEnd, int[] sortedByEndIndex, int[] sizeMap)
|
||||
{
|
||||
this.sortedByEnd = sortedByEnd;
|
||||
this.sortedByEndIndex = sortedByEndIndex;
|
||||
this.sizeMap = sizeMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size map for the branch node
|
||||
*/
|
||||
static IntervalMaxIndex getIntervalMaxIndex(Object[] branchNode)
|
||||
{
|
||||
return (IntervalMaxIndex) branchNode[branchNode.length - 1];
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,15 @@ public class LeafBTreeSearchIterator<K, V> implements BTreeSearchIterator<K, V>
|
|||
return elem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V peek()
|
||||
{
|
||||
if (!hasNext)
|
||||
throw new NoSuchElementException();
|
||||
|
||||
return (V) keys[nextPos];
|
||||
}
|
||||
|
||||
public boolean hasNext()
|
||||
{
|
||||
return hasNext;
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ class TreeCursor<K> extends NodeCursor<K>
|
|||
return;
|
||||
}
|
||||
|
||||
int[] sizeMap = getSizeMap(node);
|
||||
int[] sizeMap = sizeMap(node);
|
||||
int boundary = Arrays.binarySearch(sizeMap, relativeIndex);
|
||||
if (boundary >= 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.junit.runners.Parameterized;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.impl.IntKey;
|
||||
import accord.impl.IntKey.Routing;
|
||||
import accord.primitives.Range;
|
||||
|
|
@ -44,11 +45,21 @@ import accord.utils.Gen;
|
|||
import accord.utils.Gens;
|
||||
import accord.utils.RandomSource;
|
||||
import accord.utils.SearchableRangeList;
|
||||
import accord.utils.btree.BTree;
|
||||
import org.agrona.collections.IntArrayList;
|
||||
import org.agrona.collections.LongArrayList;
|
||||
import org.apache.cassandra.utils.btree.IntervalBTree;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import static accord.utils.Property.qt;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalEndWithKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalEndWithKeyStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalStartWithKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.intervalStartWithKeyStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyEndWithIntervalEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyEndWithIntervalStart;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyStartWithIntervalEnd;
|
||||
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndKeyComparatorHelper.keyStartWithIntervalStart;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class RangeTreeTest
|
||||
|
|
@ -157,7 +168,7 @@ public class RangeTreeTest
|
|||
// Having different models makes sure that the tree is flexiable enough and can be used with the semantics the user
|
||||
// needs (with regard to inclusivity). It also adds more confidence that the search logic is correct as different
|
||||
// algorithems help validate this.
|
||||
private enum ModelType {List, IntervalTree, SearchableRangeList}
|
||||
private enum ModelType { RTree, SearchableRangeList, IntervalTree, IntervalBTree }
|
||||
private final Pattern pattern;
|
||||
private final ModelType modelType;
|
||||
|
||||
|
|
@ -189,7 +200,7 @@ public class RangeTreeTest
|
|||
LongArrayList byRangeLength = new LongArrayList(samples * examples, -1);
|
||||
qt().withExamples(examples).check(rs -> {
|
||||
var map = create(modelType);
|
||||
var model = createModel(modelType);
|
||||
var model = createOracleForValidating(modelType);
|
||||
|
||||
Gen<Range> rangeGen = rangeGen(rs, pattern, samples);
|
||||
for (int i = 0; i < samples; i++)
|
||||
|
|
@ -199,8 +210,9 @@ public class RangeTreeTest
|
|||
map.put(range, value);
|
||||
model.put(range, value);
|
||||
}
|
||||
map.done();
|
||||
model.done();
|
||||
Assertions.assertThat(map.actual()).hasSize(samples);
|
||||
// Assertions.assertThat(map.actual()).hasSize(samples);
|
||||
if (rangeGen instanceof NoOverlap)
|
||||
((NoOverlap) rangeGen).reset();
|
||||
Gen.IntGen tokenGe = TOKEN_DISTRIBUTION.next(rs);
|
||||
|
|
@ -356,28 +368,83 @@ public class RangeTreeTest
|
|||
void done();
|
||||
}
|
||||
|
||||
private static RangeTreeModel create(ModelType modelType)
|
||||
private static Model create(ModelType modelType)
|
||||
{
|
||||
switch (modelType)
|
||||
{
|
||||
case List:
|
||||
case SearchableRangeList:
|
||||
return new RangeTreeModel(new RTree<>(COMPARATOR, END_INCLUSIVE));
|
||||
case IntervalTree: return new RangeTreeModel(new RTree<>(COMPARATOR, ALL_INCLUSIVE));
|
||||
return new SearchableRangeListModel();
|
||||
case RTree:
|
||||
return new RangeTreeModel();
|
||||
case IntervalTree:
|
||||
return new IntervalTreeModel();
|
||||
case IntervalBTree:
|
||||
return new IntervalBTreeModel();
|
||||
default:
|
||||
throw new AssertionError("Unknown type: " + modelType);
|
||||
}
|
||||
}
|
||||
|
||||
private static Model createModel(ModelType modelType)
|
||||
private static Model createOracleForValidating(ModelType modelType)
|
||||
{
|
||||
switch (modelType)
|
||||
if (modelType == ModelType.IntervalTree)
|
||||
return new ListModel();
|
||||
return new IntervalTreeModel();
|
||||
}
|
||||
|
||||
static class Entry implements Comparable<Entry>, Map.Entry<Range, Integer>
|
||||
{
|
||||
final Range range;
|
||||
final int value;
|
||||
|
||||
Entry(Range range, int value)
|
||||
{
|
||||
case List: return new ListModel();
|
||||
case SearchableRangeList: return new SearchableRangeListModel();
|
||||
case IntervalTree: return new IntervalTreeModel();
|
||||
default:
|
||||
throw new AssertionError("Unknown type: " + modelType);
|
||||
this.range = range;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry that)
|
||||
{
|
||||
return Integer.compare(this.value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range getKey()
|
||||
{
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer setValue(Integer value)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj instanceof Entry)
|
||||
return equals((Entry) obj);
|
||||
if (obj instanceof Map.Entry)
|
||||
return equals((Map.Entry) obj);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals(Entry that)
|
||||
{
|
||||
return this.range.equals(that.range) && this.value == that.value;
|
||||
}
|
||||
|
||||
public boolean equals(Map.Entry that)
|
||||
{
|
||||
return this.range.equals(that.getKey()) && that.getKey().equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -385,9 +452,9 @@ public class RangeTreeTest
|
|||
{
|
||||
private final RangeTree<Routing, Range, Integer> tree;
|
||||
|
||||
private RangeTreeModel(RangeTree<Routing, Range, Integer> tree)
|
||||
private RangeTreeModel()
|
||||
{
|
||||
this.tree = tree;
|
||||
this.tree = new RTree<>(COMPARATOR, END_INCLUSIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -434,7 +501,7 @@ public class RangeTreeTest
|
|||
@Override
|
||||
public void put(Range range, int value)
|
||||
{
|
||||
actual.add(Map.entry(range, value));
|
||||
actual.add(new Entry(range, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -456,17 +523,16 @@ public class RangeTreeTest
|
|||
@Override
|
||||
public void done()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class IntervalTreeModel implements Model
|
||||
{
|
||||
IntervalTree.Builder<Routing, Integer, Interval<Routing, Integer>> builder = IntervalTree.builder();
|
||||
IntervalTree<Routing, Integer, Interval<Routing, Integer>> actual = null;
|
||||
IntervalTree.Builder<Routing, Entry, Interval<Routing, Entry>> builder = IntervalTree.builder();
|
||||
IntervalTree<Routing, Entry, Interval<Routing, Entry>> actual = null;
|
||||
|
||||
@Override
|
||||
public IntervalTree<Routing, Integer, Interval<Routing, Integer>> actual()
|
||||
public IntervalTree<Routing, Entry, Interval<Routing, Entry>> actual()
|
||||
{
|
||||
return actual;
|
||||
}
|
||||
|
|
@ -474,7 +540,11 @@ public class RangeTreeTest
|
|||
@Override
|
||||
public void put(Range range, int value)
|
||||
{
|
||||
builder.add(new Interval<>((Routing) range.start(), (Routing) range.end(), value));
|
||||
// Interval is inclusive/inclusive, Range is exclusive/inclusive
|
||||
Routing start = (Routing) range.start();
|
||||
start = new Routing(start.key + 1);
|
||||
|
||||
builder.add(new Interval<>(start, (Routing) range.end(), new Entry(range, value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -486,12 +556,13 @@ public class RangeTreeTest
|
|||
@Override
|
||||
public List<Map.Entry<Range, Integer>> intersects(Range range)
|
||||
{
|
||||
return map(actual.matches(new Interval<>((Routing) range.start(), (Routing) range.end(), null)));
|
||||
return map(actual.matches(new Interval<>(new Routing(((Routing) range.start()).key + 1), (Routing) range.end(), null)));
|
||||
}
|
||||
|
||||
private static List<Map.Entry<Range, Integer>> map(List<Interval<Routing, Integer>> matches)
|
||||
private static List<Map.Entry<Range, Integer>> map(List<Interval<Routing, Entry>> matches)
|
||||
{
|
||||
return matches.stream().map(i -> Map.entry(IntKey.range(i.min, i.max), i.data)).collect(Collectors.toList());
|
||||
return matches.stream().map(v -> v.data)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -565,4 +636,138 @@ public class RangeTreeTest
|
|||
list = SearchableRangeList.build(this.ranges = ranges.toArray(Range[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
private static class IntervalBTreeModel implements Model
|
||||
{
|
||||
static class ItemComparators implements IntervalBTree.IntervalComparators<Item>
|
||||
{
|
||||
static final ItemComparators INSTANCE = new ItemComparators();
|
||||
|
||||
@Override
|
||||
public Comparator<Item> totalOrder()
|
||||
{
|
||||
return (a, b) -> {
|
||||
int c = a.start.compareTo(b.start);
|
||||
if (c == 0) c = a.end.compareTo(b.end);
|
||||
if (c == 0) c = Integer.compare(a.value, b.value);
|
||||
if (c == 0) c = Integer.compare(a.uniqueId, b.uniqueId);
|
||||
return c;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Item> startWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.start.compareTo(b.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Item> startWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.start.compareTo(b.end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Item> endWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.end.compareTo(b.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Item> endWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.end.compareTo(b.end);
|
||||
}
|
||||
}
|
||||
|
||||
static class ItemKeyComparators implements IntervalBTree.IntervalComparators<Object>
|
||||
{
|
||||
private static final ItemKeyComparators INSTANCE = new ItemKeyComparators();
|
||||
@Override public Comparator<Object> totalOrder() { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override
|
||||
public Comparator<Object> startWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == Item.class
|
||||
? intervalStartWithKeyStart(((Item) a).start.compareTo((RoutingKey)b))
|
||||
: keyStartWithIntervalStart(((RoutingKey)a).compareTo(((Item)b).start));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> startWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == Item.class
|
||||
? intervalStartWithKeyEnd(((Item)a).start.compareTo((RoutingKey)b))
|
||||
: keyStartWithIntervalEnd(((RoutingKey)a).compareTo(((Item)b).end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> endWithStartComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == Item.class
|
||||
? intervalEndWithKeyStart(((Item)a).end.compareTo((RoutingKey)b))
|
||||
: keyEndWithIntervalStart(((RoutingKey)a).compareTo(((Item)b).start));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Object> endWithEndComparator()
|
||||
{
|
||||
return (a, b) -> a.getClass() == Item.class
|
||||
? intervalEndWithKeyEnd(((Item)a).end.compareTo((RoutingKey)b))
|
||||
: keyEndWithIntervalEnd(((RoutingKey)a).compareTo(((Item)b).end));
|
||||
}
|
||||
}
|
||||
|
||||
static class Item extends Entry
|
||||
{
|
||||
final RoutingKey start, end;
|
||||
final int uniqueId;
|
||||
|
||||
Item(RoutingKey start, RoutingKey end, Range key, int value, int uniqueId)
|
||||
{
|
||||
super(key, value);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.uniqueId = uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] btree = IntervalBTree.empty();
|
||||
private int counter;
|
||||
|
||||
private IntervalBTreeModel()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object actual()
|
||||
{
|
||||
return btree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Range range, int value)
|
||||
{
|
||||
btree = org.apache.cassandra.utils.btree.IntervalBTree.update(btree, BTree.singleton(new Item(range.start(), range.end(), range, value, counter++)), ItemComparators.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map.Entry<Range, Integer>> intersectsToken(Routing key)
|
||||
{
|
||||
return IntervalBTree.<Object, Object, Object, List<Map.Entry<Range, Integer>>>accumulate(btree, ItemKeyComparators.INSTANCE, key, (i1, i2, item, list) -> { list.add((Item)item); return list; }, null, null, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map.Entry<Range, Integer>> intersects(Range range)
|
||||
{
|
||||
return IntervalBTree.<Item, Object, Object, List<Map.Entry<Range, Integer>>>accumulate(btree, ItemComparators.INSTANCE, new Item(range.start(), range.end(), range, 0, 0), (i1, i2, item, list) -> { list.add(item); return list; }, null, null, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ public class BTreeRemovalTest
|
|||
{
|
||||
for (int i = BTree.getChildStart(btree); i < BTree.getChildEnd(btree); ++i)
|
||||
result[i] = copy((Object[]) btree[i]);
|
||||
final int[] sizeMap = BTree.getSizeMap(btree);
|
||||
final int[] sizeMap = BTree.sizeMap(btree);
|
||||
final int[] resultSizeMap = new int[sizeMap.length];
|
||||
System.arraycopy(sizeMap, 0, resultSizeMap, 0, sizeMap.length);
|
||||
result[result.length - 1] = resultSizeMap;
|
||||
|
|
@ -96,7 +96,7 @@ public class BTreeRemovalTest
|
|||
assertEquals(expected[i], result[i]);
|
||||
for (int i = BTree.getChildStart(expected); i < BTree.getChildEnd(expected); ++i)
|
||||
assertBTree((Object[]) expected[i], (Object[]) result[i]);
|
||||
assertArrayEquals(BTree.getSizeMap(expected), BTree.getSizeMap(result));
|
||||
assertArrayEquals(BTree.sizeMap(expected), BTree.sizeMap(result));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.utils.btree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.utils.Invariants;
|
||||
|
||||
import static org.apache.cassandra.utils.btree.BTree.getChildCount;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getChildStart;
|
||||
import static org.apache.cassandra.utils.btree.BTree.getKeyEnd;
|
||||
import static org.apache.cassandra.utils.btree.BTree.isLeaf;
|
||||
|
||||
public class IntervalBTreeTest
|
||||
{
|
||||
static class TestInterval implements Comparable<TestInterval>
|
||||
{
|
||||
final int start, end;
|
||||
final int value;
|
||||
|
||||
TestInterval(int start, int end, int value)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return start + "," + end + ':' + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TestInterval that)
|
||||
{
|
||||
int c = Integer.compare(this.start, that.start);
|
||||
if (c == 0) c = Integer.compare(this.end, that.end);
|
||||
if (c == 0) c = Integer.compare(this.value, that.value);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
static class TestComparators implements IntervalBTree.IntervalComparators<TestInterval>
|
||||
{
|
||||
static final TestComparators INSTANCE = new TestComparators();
|
||||
|
||||
@Override
|
||||
public Comparator<TestInterval> totalOrder()
|
||||
{
|
||||
return TestInterval::compareTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<TestInterval> startWithStartComparator()
|
||||
{
|
||||
return (a, b) -> Integer.compare(a.start, b.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<TestInterval> startWithEndComparator()
|
||||
{
|
||||
return (a, b) -> Integer.compare(a.start, b.end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<TestInterval> endWithStartComparator()
|
||||
{
|
||||
return (a, b) -> Integer.compare(a.end, b.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<TestInterval> endWithEndComparator()
|
||||
{
|
||||
return (a, b) -> Integer.compare(a.end, b.end);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testN()
|
||||
{
|
||||
// testOne(4391017837511000309L);
|
||||
Random seeds = new Random();
|
||||
for (int i = 0 ; i < 1000 ; ++i)
|
||||
testOne(seeds.nextLong());
|
||||
}
|
||||
|
||||
public static void testOne(long seed)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<TestInterval> list = new ArrayList<>();
|
||||
Random random = new Random();
|
||||
random.setSeed(seed);
|
||||
System.out.println(seed);
|
||||
|
||||
int count = 1 << random.nextInt(11);
|
||||
int maxRemoveSize = count == 1 ? 1 : 1 + random.nextInt(count - 1);
|
||||
count = count + random.nextInt(count);
|
||||
|
||||
TreeSet<TestInterval> unique = new TreeSet<>();
|
||||
for (int i = 0 ; i < count ; ++i)
|
||||
{
|
||||
TestInterval interval = newInterval(random, 0, 10000);
|
||||
if (unique.add(interval))
|
||||
list.add(interval);
|
||||
}
|
||||
|
||||
Object[] tree = BTree.empty();
|
||||
for (TestInterval v : list)
|
||||
{
|
||||
tree = IntervalBTree.update(tree, BTree.singleton(v), TestComparators.INSTANCE);
|
||||
}
|
||||
|
||||
for (int i1 = 0 ; i1 < list.size() ; ++i1)
|
||||
{
|
||||
TestInterval iv = list.get(i1);
|
||||
TreeSet<TestInterval> collect = collect(list, 0, iv);
|
||||
remove(tree, collect, iv);
|
||||
Invariants.require(collect.isEmpty());
|
||||
iv = newInterval(random, 0, 10000);
|
||||
collect = collect(list, 0, iv);
|
||||
remove(tree, collect, iv);
|
||||
Invariants.require(collect.isEmpty());
|
||||
}
|
||||
|
||||
Collections.shuffle(list, random);
|
||||
for (int i = 0 ; i < list.size() ;)
|
||||
{
|
||||
int remaining = list.size() - i;
|
||||
int c = remaining == 1 ? 1 : 1 + random.nextInt(Math.min(remaining, maxRemoveSize));
|
||||
|
||||
TestInterval iv = list.get(i++);
|
||||
Object[] remove;
|
||||
{
|
||||
remove = IntervalBTree.singleton(iv);
|
||||
while (--c > 0)
|
||||
remove = IntervalBTree.update(remove, IntervalBTree.singleton(list.get(i++)), TestComparators.INSTANCE);
|
||||
|
||||
int notPresentCount;
|
||||
switch (random.nextInt(4))
|
||||
{
|
||||
default: throw new IllegalStateException();
|
||||
case 0: notPresentCount = 0; break;
|
||||
case 1: notPresentCount = random.nextInt(5); break;
|
||||
case 2: notPresentCount = random.nextInt(Math.max(2, count/2)); break;
|
||||
case 3: notPresentCount = random.nextInt(count*2); break;
|
||||
}
|
||||
|
||||
while (--notPresentCount > 0)
|
||||
{
|
||||
TestInterval add = newInterval(random, 0, 10000);
|
||||
if (!unique.contains(add))
|
||||
remove = IntervalBTree.update(remove, IntervalBTree.singleton(add), TestComparators.INSTANCE);
|
||||
}
|
||||
}
|
||||
tree = IntervalBTree.subtract(tree, remove, TestComparators.INSTANCE);
|
||||
validate(tree, TestComparators.INSTANCE.endWithEndComparator());
|
||||
TreeSet<TestInterval> collect = collect(list, i, iv);
|
||||
remove(tree, collect, iv);
|
||||
Invariants.require(collect.isEmpty());
|
||||
iv = newInterval(random, 0, 10000);
|
||||
collect = collect(list, i, iv);
|
||||
remove(tree, collect, iv);
|
||||
Invariants.require(collect.isEmpty());
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new AssertionError("Failed with seed " + seed, t);
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeSet<TestInterval> collect(List<TestInterval> list, int from, TestInterval intersects)
|
||||
{
|
||||
TreeSet<TestInterval> collect = new TreeSet<>();
|
||||
for (int i = from; i < list.size() ; ++i)
|
||||
{
|
||||
TestInterval iv2 = list.get(i);
|
||||
if (intersects.start < iv2.end && iv2.start < intersects.end)
|
||||
collect.add(iv2);
|
||||
}
|
||||
return collect;
|
||||
}
|
||||
|
||||
private static void remove(Object[] tree, TreeSet<TestInterval> removeFrom, TestInterval intersects)
|
||||
{
|
||||
IntervalBTree.accumulate(tree, TestComparators.INSTANCE, intersects, (c, v, i, s) -> {
|
||||
Invariants.require(c.remove(i));
|
||||
return null;
|
||||
}, removeFrom, null, null);
|
||||
}
|
||||
|
||||
private static TestInterval newInterval(Random random, int from, int to)
|
||||
{
|
||||
int end = 1 + from + random.nextInt(to - (1 + from));
|
||||
int start = from + random.nextInt(end - from);
|
||||
return new TestInterval(start, end, random.nextInt(10000));
|
||||
}
|
||||
|
||||
static Object validate(Object[] tree, Comparator endSorter)
|
||||
{
|
||||
if (isLeaf(tree))
|
||||
return max(tree, endSorter);
|
||||
|
||||
IntervalBTree.IntervalMaxIndex index = (IntervalBTree.IntervalMaxIndex) tree[tree.length - 1];
|
||||
Object[] tmp = new Object[getChildCount(tree)];
|
||||
for (int i = 0 ; i < index.sortedByEndIndex.length ; ++i)
|
||||
tmp[index.sortedByEndIndex[i]] = index.sortedByEnd[i];
|
||||
|
||||
Object max = max(tree, endSorter);
|
||||
for (int i = 0 ; i < getChildCount(tree) ; ++i)
|
||||
{
|
||||
Object childMax = validate((Object[])tree[getChildStart(tree) + i], endSorter);
|
||||
if (endSorter.compare(childMax, max) > 0)
|
||||
max = childMax;
|
||||
Invariants.require(endSorter.compare(childMax, tmp[i]) == 0);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private static Object max(Object[] tree, Comparator endSorter)
|
||||
{
|
||||
Object max = tree[0];
|
||||
for (int i = 1; i < getKeyEnd(tree) ; ++i)
|
||||
{
|
||||
if (endSorter.compare(tree[i], max) > 0)
|
||||
max = tree[i];
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue