This is an automated email from the ASF dual-hosted git repository.
benedict pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 7e32d92d06 Fix/Improve IntervalBTree - Use AsymmetricComparator for
seeking - Reimplement AbstractTransformer for more efficienct operation when
only transforming a subset (i.e. Subtraction) - Reuse source node
IntervalMaxIndex calculations for first branch level (to save iterating leaves)
7e32d92d06 is described below
commit 7e32d92d06c769093c808cd48da51ffb0410d734
Author: Benedict Elliott Smith <[email protected]>
AuthorDate: Mon Jul 7 14:36:15 2025 +0100
Fix/Improve IntervalBTree
- Use AsymmetricComparator for seeking
- Reimplement AbstractTransformer for more efficienct operation when only
transforming a subset (i.e. Subtraction)
- Reuse source node IntervalMaxIndex calculations for first branch level
(to save iterating leaves)
patch by Benedict; reviewed by Alex Petrov for CASSANDRA-20756
---
.../service/accord/CommandsForRanges.java | 65 +-
.../org/apache/cassandra/utils/IntervalTree.java | 2 +-
.../org/apache/cassandra/utils/btree/BTree.java | 749 ++++++++++++---------
.../utils/btree/FullBTreeSearchIterator.java | 5 +-
.../cassandra/utils/btree/IntervalBTree.java | 312 ++++++---
.../org/apache/cassandra/utils/RangeTreeTest.java | 83 +--
.../cassandra/utils/btree/IntervalBTreeTest.java | 159 +++--
.../utils/btree/IntervalTreePerfTest.java | 239 +++++++
8 files changed, 1028 insertions(+), 586 deletions(-)
diff --git
a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
index f484e9dc9d..aa331fbda4 100644
--- a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
+++ b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
@@ -48,21 +48,21 @@ import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.primitives.Unseekable;
import accord.primitives.Unseekables;
+import accord.utils.AsymmetricComparator;
import accord.utils.Invariants;
+import accord.utils.SymmetricComparator;
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;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.endWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyEndWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithEnd;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithEnd;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithStart;
// TODO (expected): move to accord-core, merge with existing logic there
public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements
CommandSummaries.ByTxnIdSnapshot
@@ -101,47 +101,18 @@ public class CommandsForRanges extends TreeMap<Timestamp,
Summary> implements Co
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); }
+ @Override public Comparator<TxnIdInterval> endWithEndSorter() { return
(a, b) -> a.end.compareTo(b.end); }
+
+ @Override public SymmetricComparator<TxnIdInterval>
startWithStartSeeker() { return (a, b) ->
startWithStart(a.start.compareTo(b.start)); }
+ @Override public SymmetricComparator<TxnIdInterval>
startWithEndSeeker() { return (a, b) -> startWithEnd(a.start.compareTo(b.end));
}
+ @Override public SymmetricComparator<TxnIdInterval>
endWithStartSeeker() { return (a, b) -> endWithStart(a.end.compareTo(b.start));
}
}
- static class IntervalKeyComparators implements
IntervalBTree.IntervalComparators<Object>
+ static class IntervalKeyComparators implements
IntervalBTree.WithIntervalComparators<RoutingKey, TxnIdInterval>
{
- @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));
- }
+ @Override public AsymmetricComparator<RoutingKey, TxnIdInterval>
startWithStartSeeker() { return (a, b) ->
keyStartWithStart(a.compareTo(b.start));}
+ @Override public AsymmetricComparator<RoutingKey, TxnIdInterval>
startWithEndSeeker() { return (a, b) -> keyStartWithEnd(a.compareTo(b.end)); }
+ @Override public AsymmetricComparator<RoutingKey, TxnIdInterval>
endWithStartSeeker() { return (a, b) -> keyEndWithStart(a.compareTo(b.start)); }
}
public CommandsForRanges(Map<? extends Timestamp, ? extends Summary> m)
@@ -224,7 +195,7 @@ public class CommandsForRanges extends TreeMap<Timestamp,
Summary> implements Co
case 1: return IntervalBTree.singleton(new
TxnIdInterval(route.get(0), txnId));
default:
{
- try (IntervalBTree.FastInteralTreeBuilder<TxnIdInterval>
builder = IntervalBTree.fastBuilder(COMPARATORS.endWithEndComparator()))
+ try (IntervalBTree.FastIntervalTreeBuilder<TxnIdInterval>
builder = IntervalBTree.fastBuilder(COMPARATORS))
{
for (int i = 0 ; i < size ; ++i)
builder.add(new TxnIdInterval(route.get(i),
txnId));
diff --git a/src/java/org/apache/cassandra/utils/IntervalTree.java
b/src/java/org/apache/cassandra/utils/IntervalTree.java
index 03848a4147..e1527bf377 100644
--- a/src/java/org/apache/cassandra/utils/IntervalTree.java
+++ b/src/java/org/apache/cassandra/utils/IntervalTree.java
@@ -75,7 +75,7 @@ public class IntervalTree<C extends Comparable<? super C>, D
extends Comparable<
private final I[] intervalsByMaxOrder;
@SuppressWarnings("unchecked")
- protected IntervalTree(Collection<I> intervals)
+ public IntervalTree(Collection<I> intervals)
{
this.modCount = 0;
if (intervals == null || intervals.isEmpty())
diff --git a/src/java/org/apache/cassandra/utils/btree/BTree.java
b/src/java/org/apache/cassandra/utils/btree/BTree.java
index 08372f26a5..a8de1162ce 100644
--- a/src/java/org/apache/cassandra/utils/btree/BTree.java
+++ b/src/java/org/apache/cassandra/utils/btree/BTree.java
@@ -24,6 +24,8 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import javax.annotation.Nullable;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
@@ -41,6 +43,7 @@ import org.apache.cassandra.utils.caching.TinyThreadLocalPool;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static
org.apache.cassandra.config.CassandraRelevantProperties.BTREE_BRANCH_SHIFT;
+import static org.apache.cassandra.utils.btree.BTree.Dir.ASC;
public class BTree
{
@@ -576,7 +579,7 @@ public class BTree
public static <V> Iterator<V> iterator(Object[] btree)
{
- return iterator(btree, Dir.ASC);
+ return iterator(btree, ASC);
}
public static <V> Iterator<V> iterator(Object[] btree, Dir dir)
@@ -593,7 +596,7 @@ public class BTree
public static <V> Iterable<V> iterable(Object[] btree)
{
- return iterable(btree, Dir.ASC);
+ return iterable(btree, ASC);
}
public static <V> Iterable<V> iterable(Object[] btree, Dir dir)
@@ -2339,6 +2342,8 @@ public class BTree
final LeafOrBranchBuilder child;
BranchBuilder parent;
+ @Nullable Object[] sourceNode;
+
/**
* The current buffer contents (if any) of the leaf or branch - always
sized to contain a complete
* node of the form being constructed. Always non-null, except
briefly during overflow.
@@ -2369,6 +2374,16 @@ public class BTree
this.child = child;
}
+ void setSourceNode(Object[] sourceNode)
+ {
+ this.sourceNode = sourceNode;
+ }
+
+ void clearSourceNode()
+ {
+ this.sourceNode = null;
+ }
+
/**
* Do we have enough keys in the builder to construct at least one
balanced node?
* We could have enough to build two.
@@ -2405,6 +2420,8 @@ public class BTree
return count == 0 && savedNextKey == null;
}
+ abstract void initialiseCopy(Object[] unode);
+
/**
* Drain the contents of this builder and build up to two nodes, as
necessary.
* If {@code unode != null} and we are building a single node that is
identical to it, use {@code unode} instead.
@@ -2412,7 +2429,9 @@ public class BTree
*
* @return the last node we construct
*/
- abstract Object[] drainAndPropagate(Object[] unode, BranchBuilder
propagateTo);
+ abstract Object[] drainAndPropagate(BranchBuilder propagateTo);
+
+ abstract void addKey(Object key);
/**
* Drain the contents of this builder and build at most one node.
@@ -2437,7 +2456,7 @@ public class BTree
return level.drain();
BranchBuilder parent = level.ensureParent();
- level.drainAndPropagate(null, parent);
+ level.drainAndPropagate(parent);
if (level.savedBuffer != null)
Arrays.fill(level.savedBuffer, null);
level = parent;
@@ -2541,7 +2560,8 @@ public class BTree
/**
* Add {@code nextKey} to the buffer, overflowing if necessary
*/
- public void addKey(Object nextKey)
+ @Override
+ final void addKey(Object nextKey)
{
if (count == MAX_KEYS)
overflow(nextKey);
@@ -2549,6 +2569,16 @@ public class BTree
buffer[count++] = nextKey;
}
+ final void setSourceNode(Object[] sourceNode)
+ {
+ super.setSourceNode(sourceNode);
+ }
+
+ final void clearSourceNode()
+ {
+ super.clearSourceNode();
+ }
+
/**
* Add {@code nextKey} to the buffer; the caller specifying overflow
is unnecessary
*/
@@ -2583,6 +2613,14 @@ public class BTree
}
}
+ void initialiseCopy(Object[] copy)
+ {
+ Invariants.require(isEmpty());
+ setSourceNode(copy);
+ count = sizeOfLeaf(copy);
+ System.arraycopy(copy, 0, buffer, 0, count);
+ }
+
/**
* Copy the contents of {@code source[from..to)} to {@code buffer},
overflowing as necessary.
*/
@@ -2762,7 +2800,7 @@ public class BTree
* This is only called when we have enough data to complete the node,
i.e. we have MIN_KEYS or more items added
* or the node is the BTree's root.
*/
- Object[] drainAndPropagate(Object[] unode, BranchBuilder propagateTo)
+ Object[] drainAndPropagate(BranchBuilder propagateTo)
{
Object[] leaf;
int sizeOfLeaf;
@@ -2772,10 +2810,11 @@ public class BTree
leaf = redistributeOverflowAndDrain();
sizeOfLeaf = MIN_KEYS;
}
- else if (!hasOverflow() && unode != null && count ==
sizeOfLeaf(unode) && areIdentical(buffer, 0, unode, 0, count))
+ else if (!hasOverflow() && sourceNode != null && count ==
sizeOfLeaf(sourceNode) && areIdentical(buffer, 0, sourceNode, 0, count))
{
// we have exactly the same contents as the original node, so
reuse it
- leaf = unode;
+ Invariants.require(sourceNode != null);
+ leaf = sourceNode;
sizeOfLeaf = count;
}
else
@@ -2787,10 +2826,11 @@ public class BTree
sizeOfLeaf = count;
leaf = drain();
if (allocated >= 0 && sizeOfLeaf > 0)
- allocated += ObjectSizes.sizeOfReferenceArray(sizeOfLeaf |
1) - (unode == null ? 0 : sizeOnHeapOfLeaf(unode));
+ allocated += ObjectSizes.sizeOfReferenceArray(sizeOfLeaf |
1) - (sourceNode == null ? 0 : sizeOnHeapOfLeaf(sourceNode));
}
count = 0;
+ clearSourceNode();
if (propagateTo != null)
propagateTo.addChild(leaf, sizeOfLeaf);
return leaf;
@@ -2806,9 +2846,20 @@ public class BTree
if (count == 0)
return empty();
- Object[] newLeaf = new Object[count | 1];
- System.arraycopy(buffer, 0, newLeaf, 0, count);
+ Object[] newLeaf;
+ if (sourceNode != null
+ && count == sizeOfLeaf(sourceNode)
+ && areIdentical(buffer, 0, sourceNode, 0, count))
+ {
+ newLeaf = sourceNode;
+ }
+ else
+ {
+ newLeaf = new Object[count | 1];
+ System.arraycopy(buffer, 0, newLeaf, 0, count);
+ }
count = 0;
+ clearSourceNode();
return newLeaf;
}
}
@@ -2816,6 +2867,7 @@ public class BTree
static class BranchBuilder extends LeafOrBranchBuilder
{
final LeafBuilder leaf;
+ boolean hasRightChild;
/**
* sizes of the children in {@link #buffer}. If null, we only produce
dense nodes.
@@ -2843,8 +2895,11 @@ public class BTree
* Ensure there is room to add another key to {@code
branchBuffers[branchIndex]}, and add it;
* invoke {@link #overflow} if necessary
*/
- void addKey(Object key)
+ @Override
+ final void addKey(Object key)
{
+ Invariants.require(hasRightChild);
+ hasRightChild = false;
if (count == MAX_KEYS)
overflow(key);
else
@@ -2855,9 +2910,12 @@ public class BTree
* To be invoked when there's a key already inserted to the buffer
that requires a corresponding
* right-hand child, for which the buffers are sized to ensure there
is always room.
*/
- void addChild(Object[] child, int sizeOfChild)
+ final void addChild(Object[] child, int sizeOfChild)
{
+ Invariants.require(child != null);
+ Invariants.require(!hasRightChild);
buffer[MAX_KEYS + count] = child;
+ hasRightChild = true;
recordSizeOfChild(sizeOfChild);
}
@@ -2867,10 +2925,20 @@ public class BTree
sizes[count] = sizeOfChild;
}
+ final void initialiseCopy(Object[] copy)
+ {
+ Invariants.require(isEmpty());
+ setSourceNode(copy);
+ count = shallowSizeOfBranch(copy);
+ hasRightChild = true;
+ System.arraycopy(copy, 0, buffer, 0, count);
+ System.arraycopy(copy, count, buffer, MAX_KEYS, count + 1);
+ }
+
/**
* See {@link BranchBuilder#addChild(Object[], int)}
*/
- void addChild(Object[] child)
+ final void addChild(Object[] child)
{
addChild(child, sizes == null ? 0 : size(child));
}
@@ -2878,19 +2946,16 @@ public class BTree
/**
* Insert a new child into a parent branch, when triggered by {@code
overflowLeaf} or {@code overflowBranch}
*/
- void addChildAndNextKey(Object[] newChild, int newChildSize, Object
nextKey)
+ final void addChildAndNextKey(Object[] newChild, int newChildSize,
Object nextKey)
{
- // we should always have room for a child to the right of any key
we have previously inserted
- buffer[MAX_KEYS + count] = newChild;
- recordSizeOfChild(newChildSize);
- // but there may not be room for another key
+ addChild(newChild, newChildSize);
addKey(nextKey);
}
/**
* Invoked when we want to add a key to the leaf buffer, but it is full
*/
- void propagateOverflow()
+ final void propagateOverflow()
{
// propagate the leaf we have saved in leaf().savedBuffer
if (leaf.allocated >= 0)
@@ -2908,6 +2973,8 @@ public class BTree
*/
void overflow(Object nextKey)
{
+ Invariants.require(!hasRightChild);
+
if (hasOverflow())
propagateOverflow();
@@ -3027,8 +3094,9 @@ public class BTree
* This is only called when we have enough data to complete the node,
i.e. we have MIN_KEYS or more items added
* or the node is the BTree's root.
*/
- Object[] drainAndPropagate(Object[] unode, BranchBuilder propagateTo)
+ Object[] drainAndPropagate(BranchBuilder propagateTo)
{
+ Invariants.require(hasRightChild);
int sizeOfBranch;
Object[] branch;
if (mustRedistribute())
@@ -3038,6 +3106,7 @@ public class BTree
}
else
{
+ Object[] unode = sourceNode;
int usz = unode != null ? shallowSizeOfBranch(unode) : -1;
if (!hasOverflow() && usz == count
&& areIdentical(buffer, 0, unode, 0, usz)
@@ -3053,7 +3122,7 @@ public class BTree
// the number of children here may be smaller than
MIN_KEYS if this is the root node, but there must
// be at least one key / two children.
- assert count > 0;
+ Invariants.require(count > 0);
branch = new Object[2 * (count + 1)];
System.arraycopy(buffer, 0, branch, 0, count);
System.arraycopy(buffer, MAX_KEYS, branch, count, count +
1);
@@ -3062,6 +3131,8 @@ public class BTree
}
count = 0;
+ hasRightChild = false;
+ clearSourceNode();
if (propagateTo != null)
propagateTo.addChild(branch, sizeOfBranch);
@@ -3073,23 +3144,43 @@ public class BTree
*/
Object[] drain()
{
+ Invariants.require(hasRightChild);
assert !hasOverflow();
- int keys = count;
- count = 0;
+ if (count == 0)
+ {
+ clearSourceNode();
+ hasRightChild = false;
+ return (Object[]) buffer[MAX_KEYS];
+ }
- Object[] branch = new Object[2 * (keys + 1)];
- if (keys == MAX_KEYS)
+ Object[] branch;
+ if (sourceNode != null
+ && count == shallowSizeOfBranch(sourceNode)
+ && areIdentical(buffer, 0, sourceNode, 0, count)
+ && areIdentical(buffer, MAX_KEYS, sourceNode, count, count +
1))
{
- Object[] tmp = buffer;
- buffer = branch;
- branch = tmp;
+ branch = sourceNode;
}
else
{
- System.arraycopy(buffer, 0, branch, 0, keys);
- System.arraycopy(buffer, MAX_KEYS, branch, keys, keys + 1);
+ branch = new Object[2 * (count + 1)];
+ if (count == MAX_KEYS)
+ {
+ Object[] tmp = buffer;
+ buffer = branch;
+ branch = tmp;
+ }
+ else
+ {
+ System.arraycopy(buffer, 0, branch, 0, count);
+ System.arraycopy(buffer, MAX_KEYS, branch, count, count +
1);
+ }
+ setDrainSizeMap(null, -1, branch, count);
}
- setDrainSizeMap(null, -1, branch, keys);
+
+ count = 0;
+ hasRightChild = false;
+ clearSourceNode();
return branch;
}
@@ -3198,6 +3289,7 @@ public class BTree
*/
void copyPreceding(Object[] unode, int usz, int offset, int length)
{
+ Invariants.require(!hasRightChild);
int[] uszmap = sizeMap(unode);
if (count + length > MAX_KEYS)
{
@@ -3227,6 +3319,7 @@ public class BTree
*/
private void copyPrecedingNoOverflow(Object[] unode, int usz, int[]
uszmap, int offset, int length)
{
+ Invariants.require(!hasRightChild);
if (length <= 1)
{
if (length == 0)
@@ -3266,7 +3359,9 @@ public class BTree
{
Arrays.fill(buffer, null);
count = 0;
+ hasRightChild = false;
inUse = false;
+ clearSourceNode();
}
}
@@ -3400,7 +3495,7 @@ public class BTree
BranchBuilder branch = leaf().parent;
while (branch != null && branch.inUse)
{
- assert branch.count == 0;
+ branch.count = 0;
clearBranchBuffer(branch.buffer);
if (branch.savedBuffer != null && branch.savedBuffer[0] !=
null)
Arrays.fill(branch.savedBuffer, null); // by definition
full, if non-empty
@@ -3546,8 +3641,9 @@ public class BTree
{
// ik fall inside it -- recursively merge the child with
the update, using next key as an upper bound
LeafOrBranchBuilder childBuilder = builder.child;
+ childBuilder.setSourceNode(childUNode);
ik = updateRecursive(ik, childUNode, nextUKey,
childBuilder);
- childBuilder.drainAndPropagate(childUNode, builder);
+ childBuilder.drainAndPropagate(builder);
if (find == usz) // this was the right-most child,
branch is complete and we can return immediately
return ik;
c = ik != null ? comparator.compare(nextUKey, ik) : -1;
@@ -3633,7 +3729,6 @@ public class BTree
return ik;
}
-
public void close()
{
reset();
@@ -3669,184 +3764,176 @@ public class BTree
* <p>
* The approach taken here hopefully balances simplicity, garbage
generation and execution time.
*/
- static abstract class AbstractTransformer<I, O> extends AbstractUpdater
implements AutoCloseable
+ static abstract class AbstractSeekingTransformer<I, O> extends
AbstractUpdater implements AutoCloseable
{
/**
* An iterator over the tree we are updating
*/
final SimpleTreeIterator update = new SimpleTreeIterator();
- /**
- * A queue of nodes from update that we are ready to "finish" if we
have buffered enough data from them
- * The stack pointer is maintained inside of {@link #apply()}
- */
- Object[][] queuedToFinish = new Object[1][];
-
- AbstractTransformer()
+ AbstractSeekingTransformer()
{
allocated = -1;
ensureParent();
parent.inUse = false;
}
- abstract O apply(I v);
+ abstract O apply(I in);
- Object[] apply(Object[] update)
+ LeafOrBranchBuilder initRoot(Object[] root)
{
- int height = this.update.init(update);
- if (queuedToFinish.length < height - 1)
- queuedToFinish = new Object[height - 1][];
- return apply();
+ int height = this.update.initToRoot(root);
+ LeafOrBranchBuilder level = leaf();
+ for (int d = 1; d < height; ++d)
+ level = level.ensureParent();
+ level.setSourceNode(root);
+ return level;
}
+ // for branch nodes, can seek to usz+1 indicating we should ascend
+ abstract int seekInBranch(Object[] unode, int upos, int usz);
+ // transform the leaf - false means we should directly propagate the
leaf to its parent
+ protected abstract boolean transformLeaf(Object[] unode, int upos, int
usz);
+
/**
* 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[] apply(Object[] root)
{
+ LeafOrBranchBuilder level = initRoot(root);
Object[] unode = update.node();
- int upos = update.position(), usz = sizeOfLeaf(unode);
+ int upos = update.position(), usz = shallowSize(unode);
- while (true)
+ descend: while (true)
{
- // we always start the loop on a leaf node, for both input and
output
- boolean propagatedOriginalLeaf = false;
- if (leaf().count == 0)
+ // navigate to the highest level that may need transforming
+ if (level == leaf())
{
- if (upos == 0)
- { // fast path - buffer is empty and input unconsumed,
so may be able to propagate original
- I in;
- O out;
- do
- { // optimistic loop - find first point the
transformation modified our input
- in = (I) unode[upos];
- out = apply(in);
- } while (in == out && ++upos < usz);
+ Invariants.require(level.sourceNode == unode);
+ if (transformLeaf(unode, upos, usz)) upos = usz;
+ else
+ {
+ if (!update.ascendToParent())
+ return unode;
- if ((propagatedOriginalLeaf = (upos == usz)))
- {
- // if input is unmodified by transformation,
propagate the input node
- markUsed(parent).addChild(unode, usz);
- }
- else
- {
- // otherwise copy up to the first modified portion,
- // and fall-through to our below condition for
transforming the remainder
- leaf().copyNoOverflow(unode, 0, upos++);
- if (out != null)
- leaf().addKeyNoOverflow(out);
- }
+ markUsed(parent).addChild(unode, usz);
+ level.clearSourceNode();
+ level = level.parent;
+ unode = update.node();
+ upos = update.position();
+ usz = shallowSizeOfBranch(unode);
}
-
- if (!propagatedOriginalLeaf)
- transformLeafNoOverflow(unode, upos, usz);
}
else
{
- transformLeaf(unode, upos, usz);
- }
-
- // 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;
- O nextKey;
- do
- {
- update.ascendToParent(); // always have a node above leaf
level, else we'd invoke transformLeaf
- BranchBuilder level = parent;
- unode = update.node();
- upos = update.position();
- usz = shallowSizeOfBranch(unode);
-
- while (upos == usz)
+ BranchBuilder branch = (BranchBuilder) level;
+ int pos = seekInBranch(unode, upos, usz);
+ if (pos < 0)
{
- queuedToFinish[level.height - 2] = unode;
- finishToHeight = max(finishToHeight, level.height);
+ pos = -1 -pos;
+ boolean done = pos > usz;
+ if (done)
+ pos = usz;
- if (!update.ascendToParent())
- return finishAndDrain(propagatedOriginalLeaf);
+ if (pos > upos)
+ {
+ branch.copyPreceding(unode, usz, upos, pos - upos);
+ upos = pos;
+ }
- level = level.ensureParent();
+ update.descend(unode, upos, usz);
unode = update.node();
upos = update.position();
- usz = shallowSizeOfBranch(unode);
+ usz = shallowSize(unode);
+ level = level.child;
+ level.setSourceNode(unode);
+ continue;
}
+ else
+ {
+ if (pos > upos)
+ {
+ branch.copyPreceding(unode, usz, upos, pos - upos);
+ upos = pos;
+ }
+ }
+ Invariants.require(branch.child.isEmpty());
+ branch.addChild((Object[]) unode[usz + upos]);
+ }
- nextKey = apply((I) unode[upos]);
- if (nextKey == null && leaf().count > MIN_KEYS) // if we
don't have a key, try to steal from leaf().buffer
- nextKey = (O) leaf().buffer[--leaf().count];
+ LeafOrBranchBuilder incomplete = level;
+ while (upos >= usz)
+ {
+ if (!update.ascendToParent())
+ return finishAndDrain(level);
- 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 (nextKey == null && upos < usz)
- nextKey = apply((I) unode[upos++]);
-
- // if we still found no key loop and try again on the next
parent, leaf, parent... ad infinitum
- } while (nextKey == 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}
+ usz = shallowSizeOfBranch(unode);
- if (!propagatedOriginalLeaf && !finish(leaf(), null))
- {
- leaf().addKeyNoOverflow(nextKey);
- continue;
+ if (incomplete == level && finish(level))
+ incomplete = level.parent;
+ level = level.parent;
}
- BranchBuilder finish = parent;
- while (true)
+ O next = apply((I) unode[upos++]);
+ if (next == null)
{
- if (finish.height <= finishToHeight)
+ // next has been filtered, so look for the (unfiltered)
successor node
+ successor: while (true)
{
- Object[] originalNode = queuedToFinish[finish.height -
2];
- if (finish(finish, originalNode))
+ level = descend(level, leaf(), unode, upos, usz);
+ unode = update.node();
+ upos = update.position();
+ usz = sizeOfLeaf(unode);
+
+ while (upos < usz)
{
- finish = finish.parent;
- continue;
+ if (null != (next = apply((I)unode[upos++])))
+ break successor;
}
- }
- // add our key to the last unpropagated parent branch
buffer
- finish.addKey(nextKey);
- break;
+ do
+ {
+ if (!update.ascendToParent())
+ return finishAndDrain(level);
+
+ unode = update.node();
+ upos = update.position();
+ usz = shallowSizeOfBranch(unode);
+ level = level.parent;
+ } while (upos >= usz);
+
+ next = apply((I) unode[upos++]);
+ if (next != null)
+ break;
+ }
}
- }
- }
- private void transformLeafNoOverflow(Object[] unode, int upos, int usz)
- {
- while (upos < usz)
- {
- O v = apply((I) unode[upos++]);
- leaf().maybeAddKeyNoOverflow(v);
+ incomplete.addKey(next);
+ if (level.height > incomplete.height)
+ {
+ level = descend(level, incomplete, unode, upos, usz);
+ unode = update.node();
+ upos = update.position();
+ usz = shallowSize(unode);
+ }
}
}
- private void transformLeaf(Object[] unode, int upos, int usz)
+ LeafOrBranchBuilder descend(LeafOrBranchBuilder from,
LeafOrBranchBuilder to, Object[] unode, int upos, int usz)
{
- while (upos < usz)
+ while (update.height() > to.height)
{
- O v = apply((I) unode[upos++]);
- leaf().maybeAddKey(v);
+ update.descend(unode, upos, usz);
+ unode = update.node();
+ upos = update.position();
+ usz = shallowSize(unode);
+ from = from.child;
+ from.setSourceNode(unode);
}
+ return to;
}
/**
@@ -3854,12 +3941,12 @@ 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.
*/
- final boolean finish(LeafOrBranchBuilder level, Object[] unode)
+ final boolean finish(LeafOrBranchBuilder level)
{
if (!level.isSufficient())
return false;
- level.drainAndPropagate(unode, level.ensureParent());
+ level.drainAndPropagate(level.ensureParent());
return true;
}
@@ -3871,16 +3958,56 @@ 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.
*/
- final Object[] finishAndDrain(boolean skipLeaf)
+ final Object[] finishAndDrain(LeafOrBranchBuilder root)
{
- LeafOrBranchBuilder level = leaf();
- if (skipLeaf)
+ if (root == leaf())
+ return root.completeBuild();
+
+ BranchBuilder branch = (BranchBuilder) root;
+ // the logic after this loop expects that all builders are
populated and have the same number of keys as children
+ // so this loop simply checks to see if we can build without any
rebalancing,
+ // stopping at the first balanced (hasRightChild) and sufficient
for building branch.
+ // Otherwise it un
+ while (true)
{
- level = nonEmptyParentMaybeSteal(level);
- // handle an edge case, where we have propagated a single
complete leaf but have no other contents in any parent
- if (level == null)
- return (Object[]) leaf().parent.buffer[MAX_KEYS];
+ if (branch.hasRightChild)
+ {
+ for (LeafOrBranchBuilder child = branch.child ; child !=
null ; child = child.child)
+ Invariants.require(child.isEmpty());
+
+ boolean isSufficient = true;
+ for (BranchBuilder b = branch ; b != root && isSufficient
; b = b.parent)
+ isSufficient = b.isSufficient();
+
+ if (isSufficient)
+ {
+ for (BranchBuilder b = branch ; b != root ; b =
b.parent)
+ b.drainAndPropagate(b.parent);
+ return root.completeBuild();
+ }
+ else
+ {
+ while (true)
+ {
+ Object[] rightChild = (Object[])
branch.buffer[MAX_KEYS + branch.count];
+ branch.hasRightChild = false;
+ branch.child.initialiseCopy(rightChild);
+
+ if (branch.child == leaf())
+ break;
+ branch = (BranchBuilder) branch.child;
+ }
+ }
+ break;
+ }
+
+ if (branch.child == leaf())
+ break;
+
+ branch = (BranchBuilder) branch.child;
}
+
+ LeafOrBranchBuilder level = leaf();
while (true)
{
BranchBuilder parent = nonEmptyParentMaybeSteal(level);
@@ -3892,9 +4019,7 @@ public class BTree
}
else
{
-
- Object[] originalNode = level == leaf() ? null :
queuedToFinish[level.height - 2];
- Object[] result = level.drainAndPropagate(originalNode,
parent);
+ Object[] result = level.drainAndPropagate(parent);
if (parent == null)
return result;
}
@@ -3934,7 +4059,7 @@ public class BTree
if (exhausted)
return fill.drain();
- fill.drainAndPropagate(null, parent);
+ fill.drainAndPropagate(parent);
return null;
}
@@ -3982,7 +4107,6 @@ public class BTree
void reset()
{
- Arrays.fill(queuedToFinish, 0, update.leafDepth, null);
update.reset();
super.reset();
}
@@ -3990,8 +4114,10 @@ public class BTree
/**
* Implement set subtraction/difference using a modified version of the
Transformer logic
+ *
+ * TODO (desired): merge with Transformer
*/
- static abstract class Subtraction<K, T extends K> extends
AbstractTransformer<T, T> implements AutoCloseable
+ static abstract class AbstractSubtraction<K, T extends K> extends
AbstractSeekingTransformer<T, T> implements AutoCloseable
{
/**
* An iterator over the tree we are updating
@@ -3999,187 +4125,119 @@ public class BTree
PeekingSearchIterator<K, ? extends K> remove;
Comparator<K> comparator;
- Object[] apply(Object[] update, PeekingSearchIterator<K, ? extends K>
remove)
+ Object[] subtract(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();
+ return apply(update);
+ }
+
+ Object[] subtract(Object[] update, Object[] remove)
+ {
+ return subtract(update, slice(remove, comparator, ASC));
}
@Override
T apply(T v)
{
- throw new UnsupportedOperationException();
+ return remove.next(v) == null ? v : null;
}
- /**
- * 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()
+ @Override
+ int seekInBranch(Object[] unode, int upos, int usz)
{
- 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;
- if (!remove.hasNext() || comparator.compare((K)unode[usz -
1], remove.peek()) < 0)
- {
- // short-circuit common case of removal not
intersecting the current node, by comparing with last key
- upos = usz;
- }
+ if (!remove.hasNext())
+ return -1 - (1 + usz);
- 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 i = exponentialSearch(comparator, unode, upos, usz,
remove.peek());
+ if (i == -1 - usz)
+ {
+ // if we sort after the last key in the branch, we may need to
descend into the right-most child
+ // but, for all branches besides the root we can try to look
at a parent node to decide this.
+ // we only look at the direct parent, and if we are its
right-most child already, we just assume we must descend
+ int pdepth = update.depth - 1;
+ if (pdepth >= 0)
{
- int prev = upos;
- while (upos < usz)
+ Object[] pnode = update.nodes[pdepth];
+ int ppos = update.positions[pdepth];
+ if (ppos < shallowSizeOfBranch(pnode) &&
comparator.compare(remove.peek(), (K)pnode[ppos]) >= 0)
{
- 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;
+ // increase our result index to point to *after* the
last child;
+ // (it's an inequality binary search semantic answer,
so will be negated)
+ --i;
}
- leaf().copy(unode, prev, usz - prev);
}
+ }
+ return i;
+ }
- // 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
+ @Override
+ protected boolean transformLeaf(Object[] unode, int upos, int usz)
+ {
+ int prev = upos;
+ if (remove.hasNext())
+ {
+ while (true)
{
- if (!update.ascendToParent())
- return finishAndDrain(propagatedOriginalLeaf);
-
- BranchBuilder level = parent;
- unode = update.node();
- upos = update.position();
- usz = shallowSizeOfBranch(unode);
-
- while (upos == usz)
+ // fast path - buffer is empty and input unconsumed, so
may be able to propagate original
+ upos = exponentialSearch(comparator, unode, upos, usz,
remove.peek());
+ if (upos < 0)
{
- 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);
+ upos = -1 - upos;
+ if (upos == usz)
+ break;
+ remove.next();
+ if (remove.hasNext()) continue;
+ else break;
}
- 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;
- }
+ leaf().copy(unode, prev, upos - prev);
+ ++upos;
+ prev = upos;
+ remove.next();
+ if (!remove.hasNext())
+ break;
+ }
+ }
- // if we still found no key loop and try again on the next
parent, leaf, parent... ad infinitum
- } while (next == null);
+ if (prev == 0 && leaf().isEmpty())
+ // if input is unmodified by transformation, propagate the
input node
+ return false;
- // we always end with unode a leaf, though it may be that upos
== usz and that we will do nothing with it
+ leaf().copy(unode, prev, usz - prev);
+ return true;
+ }
+ }
- // 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}
+ private static abstract class AbstractTransformer<I, O> extends
AbstractSeekingTransformer<I, O>
+ {
+ @Override
+ final int seekInBranch(Object[] unode, int upos, int usz)
+ {
+ return -1 - upos;
+ }
- if (!propagatedOriginalLeaf && !finish(leaf(), null))
+ protected final boolean transformLeaf(Object[] unode, int upos, int
usz)
+ {
+ if (leaf().count == 0)
+ {
+ while (upos < usz)
{
- leaf().addKeyNoOverflow(next);
- continue;
+ O v = apply((I) unode[upos++]);
+ leaf().maybeAddKeyNoOverflow(v);
}
-
- BranchBuilder finish = parent;
- while (true)
+ }
+ else
+ {
+ while (upos < usz)
{
- 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;
+ O v = apply((I) unode[upos++]);
+ leaf().maybeAddKey(v);
}
}
+ return true;
}
}
-
private static class Transformer<I, O> extends AbstractTransformer<I, O>
{
static final TinyThreadLocalPool<Transformer> POOL = new
TinyThreadLocalPool<>();
@@ -4284,15 +4342,9 @@ public class BTree
// Begins by immediately descending to first leaf; if empty terminates
immediately.
private static class SimpleTreeIterator extends SimpleTreeStack
{
- int init(Object[] tree)
+ int initToLeaf(Object[] tree)
{
- int maxHeight = maxRootHeight(size(tree));
- if (positions == null || maxHeight >= positions.length)
- {
- positions = new int[maxHeight + 1];
- nodes = new Object[maxHeight + 1][];
- }
- nodes[0] = tree;
+ init(tree);
if (isEmpty(tree))
{
// already done
@@ -4314,6 +4366,35 @@ public class BTree
return leafDepth + 1;
}
+ int initToRoot(Object[] tree)
+ {
+ init(tree);
+ positions[0] = 0;
+ leafDepth = depth = 0;
+ while (!isLeaf(tree))
+ {
+ tree = (Object[]) tree[shallowSizeOfBranch(tree)];
+ ++leafDepth;
+ }
+ return leafDepth + 1;
+ }
+
+ int height()
+ {
+ return 1 + leafDepth - depth;
+ }
+
+ void init(Object[] tree)
+ {
+ int maxHeight = maxRootHeight(size(tree));
+ if (positions == null || maxHeight >= positions.length)
+ {
+ positions = new int[maxHeight + 1];
+ nodes = new Object[maxHeight + 1][];
+ }
+ nodes[0] = tree;
+ }
+
void descendIntoNextLeaf(Object[] node, int pos, int sz)
{
positions[depth] = ++pos;
@@ -4328,6 +4409,16 @@ public class BTree
}
}
+ // return true iff entered leaf depth
+ boolean descend(Object[] node, int pos, int sz)
+ {
+ positions[depth] = pos;
+ ++depth;
+ nodes[depth] = (Object[]) node[sz + pos];
+ positions[depth] = 0;
+ return depth == leafDepth;
+ }
+
boolean ascendToParent()
{
if (depth < 0)
diff --git
a/src/java/org/apache/cassandra/utils/btree/FullBTreeSearchIterator.java
b/src/java/org/apache/cassandra/utils/btree/FullBTreeSearchIterator.java
index 7c2fd628c0..546fad8fa3 100644
--- a/src/java/org/apache/cassandra/utils/btree/FullBTreeSearchIterator.java
+++ b/src/java/org/apache/cassandra/utils/btree/FullBTreeSearchIterator.java
@@ -22,6 +22,8 @@ import static org.apache.cassandra.utils.btree.BTree.size;
import java.util.Comparator;
import java.util.NoSuchElementException;
+import accord.utils.Invariants;
+
public class FullBTreeSearchIterator<K, V> extends TreeCursor<K> implements
BTreeSearchIterator<K, V>
{
private final boolean forwards;
@@ -109,7 +111,8 @@ public class FullBTreeSearchIterator<K, V> extends
TreeCursor<K> implements BTre
if ((state & ON_ITEM) == 1)
{
state &= ~ON_ITEM;
- index = moveOne(forwards);
+ if (compareToLast(index = moveOne(forwards)) >= 0)
+ state = LAST;
}
return (V) currentValue();
}
diff --git a/src/java/org/apache/cassandra/utils/btree/IntervalBTree.java
b/src/java/org/apache/cassandra/utils/btree/IntervalBTree.java
index f04cf08917..7ea8b81989 100644
--- a/src/java/org/apache/cassandra/utils/btree/IntervalBTree.java
+++ b/src/java/org/apache/cassandra/utils/btree/IntervalBTree.java
@@ -18,16 +18,23 @@
package org.apache.cassandra.utils.btree;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
+import javax.annotation.Nullable;
+
+import accord.utils.AsymmetricComparator;
import accord.utils.Invariants;
import accord.utils.QuadFunction;
+import accord.utils.SortedArrays;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.utils.caching.TinyThreadLocalPool;
+import static accord.utils.SortedArrays.Search.CEIL;
+import static accord.utils.SortedArrays.Search.FLOOR;
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;
@@ -37,7 +44,6 @@ 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.
@@ -50,25 +56,42 @@ import static org.apache.cassandra.utils.btree.BTree.slice;
*/
public class IntervalBTree
{
- public interface IntervalComparators<V>
+ /**
+ * Defines additional SORT comparators that must be symmetric for
building/maintaining an interval tree
+ */
+ public interface IntervalComparators<I> extends WithIntervalComparators<I,
I>
{
- Comparator<V> totalOrder();
- Comparator<V> startWithStartComparator();
- Comparator<V> startWithEndComparator();
- Comparator<V> endWithStartComparator();
- Comparator<V> endWithEndComparator();
+ Comparator<I> totalOrder();
+ Comparator<I> endWithEndSorter();
}
- public static class InclusiveEndKeyComparatorHelper
+ /**
+ * Defines search comparators for finding an element in an IntervalBTree.
+ *
+ * A search for an element K will potentially include any interval I
+ * for which any comparator yields compare(K, I) == 0.
+ *
+ * These comparators DO NOT need to be symmetric in either type OR
comparison result,
+ * and should knowingly impose the inclusive/exclusive criteria of the
interval
+ * by returning non-zero answers as appropriate.
+ *
+ * Specifically, compare(a, a) should not return 0 if the relevant bound
is exclusive.
+ */
+ public interface WithIntervalComparators<K, I>
{
- 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); }
+ AsymmetricComparator<K, I> startWithStartSeeker();
+ AsymmetricComparator<K, I> startWithEndSeeker();
+ AsymmetricComparator<K, I> endWithStartSeeker();
+ }
+
+ public static class InclusiveEndHelper
+ {
+ public static int keyStartWithStart(int c) { return
equalsMeansAfter(c); }
+ public static int keyStartWithEnd(int c) { return c; }
+ public static int keyEndWithStart(int c) { return
equalsMeansBefore(c); }
+ public static int startWithStart(int c) { return equalsMeansAfter(c); }
+ public static int startWithEnd(int c) { return equalsMeansAfter(c); }
+ public static int endWithStart(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; }
}
@@ -76,52 +99,55 @@ public class IntervalBTree
/**
* 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)
+ public static <Find, Interval, P1, P2, V> V accumulate(Object[] btree,
WithIntervalComparators<Find, Interval> comparators, Find find,
QuadFunction<P1, P2, Interval, V, V> function, P1 p1, P2 p2, V accumulate)
{
if (isLeaf(btree))
{
- Comparator comparator = comparators.startWithEndComparator();
+ AsymmetricComparator<Find, Interval> startWithEnd =
comparators.startWithEndSeeker();
+ AsymmetricComparator<Find, Interval> endWithStart =
comparators.endWithStartSeeker();
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);
+ Interval interval = (Interval) btree[i];
+ if (endWithStart.compare(find, interval) >= 0 &&
startWithEnd.compare(find, interval) <= 0)
+ accumulate = function.apply(p1, p2, interval, accumulate);
}
}
else
{
- int startKey = Arrays.binarySearch(btree, 0, getKeyEnd(btree),
intersects, (Comparator) comparators.startWithStartComparator());
+ // start/end represent those keys/children we are guaranteed to
intersect (and so may visit without any further comparisons)
+ int startKey = SortedArrays.binarySearch(btree, 0,
getKeyEnd(btree), find,
(AsymmetricComparator)comparators.startWithStartSeeker(), CEIL);
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 endKey = SortedArrays.binarySearch(btree, startKey,
getKeyEnd(btree), find, (AsymmetricComparator)comparators.endWithStartSeeker(),
FLOOR);
int endChild;
- if (endKey >= 0) endChild = 1 + endKey;
+ if (endKey >= 0) { if (endKey < getKeyEnd(btree)) ++endKey;
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);
+ accumulate = accumulateMaxOnly(startChild + (startChild ==
endChild ? 1 : 0), startKey, btree, comparators, find, 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);
+ accumulate = accumulate((Object[]) btree[childOffset +
startChild++], comparators, find, function, p1, p2, accumulate);
if (startKey < startChild && startKey < endKey)
- accumulate = function.apply(p1, p2, (R) btree[startKey++],
accumulate);
+ accumulate = function.apply(p1, p2, (Interval)
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);
+ accumulate = function.apply(p1, p2, (Interval)
btree[startKey++], accumulate);
}
if (startKey < endKey)
- accumulate = function.apply(p1, p2, (R) btree[startKey],
accumulate);
+ accumulate = function.apply(p1, p2, (Interval)
btree[startKey], accumulate);
if (startChild < endChild)
- accumulate = accumulate((Object[]) btree[childOffset +
startChild], comparators, intersects, function, p1, p2, accumulate);
+ accumulate = accumulate((Object[]) btree[childOffset +
startChild], comparators, find, function, p1, p2, accumulate);
}
return accumulate;
}
@@ -146,45 +172,41 @@ public class IntervalBTree
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)
+ private static <Find, Interval, P1, P2, V> V accumulateMaxOnly(int
ifChildBefore, int ifKeyBefore, Object[] btree, WithIntervalComparators<Find,
Interval> comparators, Find find, QuadFunction<P1, P2, Interval, V, V>
function, P1 p1, P2 p2, V accumulate)
{
+ AsymmetricComparator<Find, Interval> startWithEnd =
comparators.startWithEndSeeker();
+ AsymmetricComparator<Find, Interval> endWithStart =
comparators.endWithStartSeeker();
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);
- }
+ Interval interval = (Interval) btree[i];
+ if (startWithEnd.compare(find, interval) <= 0 &&
endWithStart.compare(find, interval) >= 0)
+ accumulate = function.apply(p1, p2, interval, accumulate);
}
}
else
{
int keyEnd = getChildStart(btree);
+ ifKeyBefore = Math.min(ifKeyBefore, keyEnd);
+
IntervalMaxIndex intervalMaxIndex = getIntervalMaxIndex(btree);
- int descendMaxStart =
Arrays.binarySearch(intervalMaxIndex.sortedByEnd, intersects, (Comparator)
comparators.endWithStartComparator());
+ int descendMaxStart = SortedArrays.binarySearch((Interval[])
intervalMaxIndex.sortedByEnd, 0, intervalMaxIndex.sortedByEnd.length, find,
comparators.startWithEndSeeker(), CEIL);
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)
+ if (index < ifKeyBefore)
{
- Invariants.paranoid(comparator.compare(v, intersects) < 0);
- accumulate = function.apply(p1, p2, v, accumulate);
+ Interval interval = (Interval) btree[index];
+ if (startWithEnd.compare(find, interval) <= 0 &&
endWithStart.compare(find, interval) >= 0)
+ accumulate = function.apply(p1, p2, interval,
accumulate);
}
+ if (index < ifChildBefore)
+ accumulate = accumulateMaxOnly(Integer.MAX_VALUE,
Integer.MAX_VALUE, (Object[]) btree[keyEnd + index], comparators, find,
function, p1, p2, accumulate);
}
}
return accumulate;
@@ -198,19 +220,107 @@ public class IntervalBTree
int index;
}
+ Object[] unpackedIndex;
SortEntry[] sort = new SortEntry[BRANCH_FACTOR];
- Comparator endSorter;
+ Comparator endSorter, totalOrder;
- public void override(Object[] branch)
+ void setComparators(IntervalComparators comparators)
+ {
+ this.endSorter = comparators.endWithEndSorter();
+ this.totalOrder = comparators.totalOrder();
+ }
+
+ void reset()
+ {
+ this.endSorter = this.totalOrder = null;
+ for (int i = 0 ; i < sort.length && sort[i] != null ; i++)
+ sort[i].sort = null;
+ }
+
+ public void override(Object[] branch, @Nullable List<Object[]>
sourceNodes)
{
- int[] sizeMap = sizeMap(branch);
int childStart = getChildStart(branch), childCount =
getChildCount(branch);
+ for (int i = childCount - 1 ; i >= 0 && sort[i] == null ; --i)
+ sort[i] = new SortEntry();
+
+ if (sourceNodes != null && sourceNodes.isEmpty())
+ sourceNodes = null;
+
+ if (sourceNodes != null)
+ {
+ int maxUnpackedSize = 0;
+ if (unpackedIndex == null)
+ unpackedIndex = new Object[BRANCH_FACTOR];
+
+ int i = 0;
+ for (Object[] sn : sourceNodes)
+ {
+ IntervalMaxIndex index = getIntervalMaxIndex(sn);
+ int snStart = getChildStart(sn), snCount =
getChildCount(sn);
+ for (int j = 0 ; j < index.sortedByEnd.length ; ++j)
+ unpackedIndex[index.sortedByEndIndex[j]] =
index.sortedByEnd[j];
+
+ int j = 0;
+ while (i < childCount && j < snCount)
+ {
+ if (branch[childStart + i] == sn[snStart + j]) //
child nodes are the same so we can copy max
+ {
+ sort[i].index = i;
+ if (j == snStart)
+ {
+ // in either case, the max we have represents
the max of the child;
+ // must only compare with new branch key (if
any) and take the branch if ends later
+ sort[i].sort = i == childStart ||
endSorter.compare(branch[i], unpackedIndex[j]) <= 0
+ ? unpackedIndex[j] : branch[i];
+ }
+ else if (unpackedIndex[j] != sn[j])
+ {
+ // previous max was not the branch, so must
have been within child
+ // like prior condition, must only compare
with new branch key (if any) and take the branch if ends later
+ // however, we have a chance to save a
comparison here if we also copied the key from the source node
+ sort[i].sort = i == childStart || branch[i] ==
sn[j] || endSorter.compare(branch[i], unpackedIndex[j]) <= 0
+ ? unpackedIndex[j] : branch[i];
+ }
+ else if (i != childStart &&
endSorter.compare(sn[j], branch[i]) <= 0)
+ {
+ // previous max was previous branch key; new
branch key has higher end so can simply use it
+ sort[i].sort = branch[i];
+ }
+ else
+ {
+ // otherwise we cannot infer anything directly
+ sort[i].sort = null;
+ }
+
+ ++i;
+ ++j;
+ }
+ else
+ {
+ if (i == childStart || j == snStart)
+ break; // if we've finished iteration of one
or the other, break and continue with any further source nodes (if any)
+
+ int c = totalOrder.compare(branch[i], sn[j]);
+ if (c <= 0) sort[i++].sort = null;
+ if (c >= 0) ++j;
+ }
+ }
+
+ maxUnpackedSize = Math.max(maxUnpackedSize, snCount);
+ }
+ while (i < childCount)
+ sort[i++].sort = null;
+ Arrays.fill(unpackedIndex, 0, maxUnpackedSize, null);
+ }
for (int i = 0; i < childCount; ++i)
{
+ if (sourceNodes != null && sort[i].sort != null)
+ continue;
+
Object[] child = (Object[]) branch[i + childStart];
Object max = maxChild(child);
- if (sort[i] == null)
- sort[i] = new SortEntry();
+ if (i < childStart && endSorter.compare(branch[i], max) > 0)
+ max = branch[i];
sort[i].index = i;
sort[i].sort = max;
}
@@ -222,25 +332,28 @@ public class IntervalBTree
sortedByEnd[i] = sort[i].sort;
sortedByEndIndex[i] = sort[i].index;
}
+
+ int[] sizeMap = sizeMap(branch);
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 (isLeaf(child))
{
- if (endSorter.compare(child[j], max) > 0)
- max = child[j];
+ Object max = child[0];
+ for (int i = 1, end = getLeafKeyEnd(child); i < end; ++i)
+ {
+ if (endSorter.compare(child[i], max) > 0)
+ max = child[i];
+ }
+ return max;
}
- if (!isLeaf(child))
+ else
{
- IntervalMaxIndex childIndex = getIntervalMaxIndex(child);
- Object maxChild =
childIndex.sortedByEnd[childIndex.sortedByEnd.length - 1];
- if (endSorter.compare(maxChild, max) > 0)
- max = maxChild;
+ IntervalMaxIndex index = getIntervalMaxIndex(child);
+ return index.sortedByEnd[index.sortedByEnd.length - 1];
}
- return max;
}
@Override
@@ -252,11 +365,13 @@ public class IntervalBTree
static class IntervalBranchBuilder extends BTree.BranchBuilder
{
+ List<Object[]> sourceNodes;
IntervalIndexAdapter adapter;
IntervalBranchBuilder(BTree.LeafOrBranchBuilder child)
{
super(child);
+ sourceNodes = child.height == 1 ? new ArrayList<>() : null;
}
@Override
@@ -282,7 +397,7 @@ public class IntervalBTree
int setDrainSizeMap(Object[] original, int keysInOriginal, Object[]
branch, int keysInBranch)
{
int result = super.setDrainSizeMap(original, keysInOriginal,
branch, keysInBranch);
- adapter.override(branch);
+ adapter.override(branch, sourceNodes);
return result;
}
@@ -290,21 +405,37 @@ public class IntervalBTree
void setRedistributedSizeMap(Object[] branch, int steal)
{
super.setRedistributedSizeMap(branch, steal);
- adapter.override(branch);
+ adapter.override(branch, sourceNodes);
}
@Override
int setOverflowSizeMap(Object[] branch, int keys)
{
int result = super.setOverflowSizeMap(branch, keys);
- adapter.override(branch);
+ adapter.override(branch, sourceNodes);
return result;
}
+
+ @Override
+ void setSourceNode(Object[] sourceNode)
+ {
+ if (sourceNodes != null)
+ sourceNodes.add(sourceNode);
+ super.setSourceNode(sourceNode);
+ }
+
+ @Override
+ void clearSourceNode()
+ {
+ super.clearSourceNode();
+ if (sourceNodes != null)
+ sourceNodes.clear();
+ }
}
- public static class FastInteralTreeBuilder<V> extends BTree.FastBuilder<V>
+ public static class FastIntervalTreeBuilder<V> extends BTree.FastBuilder<V>
{
- private static final TinyThreadLocalPool<FastInteralTreeBuilder<?>>
POOL = new TinyThreadLocalPool<>();
+ private static final TinyThreadLocalPool<FastIntervalTreeBuilder<?>>
POOL = new TinyThreadLocalPool<>();
final IntervalIndexAdapter adapter = new IntervalIndexAdapter();
@Override
@@ -319,6 +450,13 @@ public class IntervalBTree
super.initParent();
((IntervalBranchBuilder) parent).init(adapter);
}
+
+ @Override
+ public void close()
+ {
+ super.close();
+ adapter.reset();
+ }
}
static class IntervalUpdater<Compare, Existing extends Compare, Insert
extends Compare> extends BTree.Updater<Compare, Existing, Insert>
@@ -326,14 +464,14 @@ public class IntervalBTree
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)
+ static <Compare, Existing extends Compare, Insert extends Compare>
IntervalUpdater<Compare, Existing, Insert> get(IntervalComparators<Compare>
comparators)
{
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;
+ updater.adapter.setComparators(comparators);
return updater;
}
@@ -353,7 +491,7 @@ public class IntervalBTree
@Override
public void close()
{
- adapter.endSorter = null;
+ adapter.reset();
}
}
@@ -367,23 +505,18 @@ public class IntervalBTree
return BTree.singleton(value);
}
- static class Subtraction<K, T extends K> extends BTree.Subtraction<K, T>
+ static class Subtraction<K, T extends K> extends
BTree.AbstractSubtraction<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();
+ subtraction.adapter.setComparators(comparators);
return subtraction;
}
@@ -405,7 +538,7 @@ public class IntervalBTree
{
reset();
comparator = null;
- adapter.endSorter = null;
+ adapter.reset();
}
}
@@ -417,9 +550,9 @@ public class IntervalBTree
*/
public static <Compare> Object[] subtract(Object[] toUpdate, Object[]
subtract, IntervalComparators<Compare> comparators)
{
- try (Subtraction subtraction =
IntervalBTree.Subtraction.get(comparators))
+ try (Subtraction subtraction = Subtraction.get(comparators))
{
- return subtraction.apply(toUpdate, slice(subtract,
comparators.totalOrder(), ASC));
+ return subtraction.subtract(toUpdate, subtract);
}
}
@@ -456,24 +589,23 @@ public class IntervalBTree
}
}
- try (IntervalUpdater<Compare, Existing, Insert> updater =
IntervalUpdater.get(comparators.endWithEndComparator()))
+ try (IntervalUpdater<Compare, Existing, Insert> updater =
IntervalUpdater.get(comparators))
{
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)
+ public static <V> FastIntervalTreeBuilder<V>
fastBuilder(IntervalComparators<V> comparators)
{
- TinyThreadLocalPool.TinyPool<FastInteralTreeBuilder<?>> pool =
FastInteralTreeBuilder.POOL.get();
- FastInteralTreeBuilder<V> builder = (FastInteralTreeBuilder<V>)
pool.poll();
+ TinyThreadLocalPool.TinyPool<FastIntervalTreeBuilder<?>> pool =
FastIntervalTreeBuilder.POOL.get();
+ FastIntervalTreeBuilder<V> builder = (FastIntervalTreeBuilder<V>)
pool.poll();
if (builder == null)
- builder = new FastInteralTreeBuilder<>();
+ builder = new FastIntervalTreeBuilder<>();
builder.pool = pool;
- builder.adapter.endSorter = endSorter;
+ builder.adapter.setComparators(comparators);
return builder;
}
diff --git a/test/unit/org/apache/cassandra/utils/RangeTreeTest.java
b/test/unit/org/apache/cassandra/utils/RangeTreeTest.java
index e576d667c2..48cd22d429 100644
--- a/test/unit/org/apache/cassandra/utils/RangeTreeTest.java
+++ b/test/unit/org/apache/cassandra/utils/RangeTreeTest.java
@@ -41,10 +41,12 @@ import accord.api.RoutingKey;
import accord.impl.IntKey;
import accord.impl.IntKey.Routing;
import accord.primitives.Range;
+import accord.utils.AsymmetricComparator;
import accord.utils.Gen;
import accord.utils.Gens;
import accord.utils.RandomSource;
import accord.utils.SearchableRangeList;
+import accord.utils.SymmetricComparator;
import accord.utils.btree.BTree;
import org.agrona.collections.IntArrayList;
import org.agrona.collections.LongArrayList;
@@ -52,14 +54,12 @@ 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;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.endWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyEndWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithEnd;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithEnd;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithStart;
@RunWith(Parameterized.class)
public class RangeTreeTest
@@ -655,67 +655,20 @@ public class RangeTreeTest
};
}
- @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 SymmetricComparator<Item> endWithEndSorter() {
return (a, b) -> a.end.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);
- }
+ @Override public SymmetricComparator<Item> startWithStartSeeker()
{ return (a, b) -> startWithStart(a.start.compareTo(b.start)); }
+ @Override public SymmetricComparator<Item> startWithEndSeeker() {
return (a, b) -> startWithEnd(a.start.compareTo(b.end)); }
+ @Override public SymmetricComparator<Item> endWithStartSeeker() {
return (a, b) -> endWithStart(a.end.compareTo(b.start)); }
}
- static class ItemKeyComparators implements
IntervalBTree.IntervalComparators<Object>
+ static class ItemKeyComparators implements
IntervalBTree.WithIntervalComparators<RoutingKey, Item>
{
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));
- }
+ @Override public AsymmetricComparator<RoutingKey, Item>
startWithStartSeeker() { return (a, b) ->
keyStartWithStart(a.compareTo(b.start)); }
+ @Override public AsymmetricComparator<RoutingKey, Item>
startWithEndSeeker() { return (a, b) -> keyStartWithEnd(a.compareTo(b.end)); }
+ @Override public AsymmetricComparator<RoutingKey, Item>
endWithStartSeeker() { return (a, b) -> keyEndWithStart(a.compareTo(b.start)); }
}
static class Item extends Entry
@@ -754,13 +707,13 @@ public class RangeTreeTest
@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<>());
+ return IntervalBTree.<RoutingKey, Item, 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<>());
+ return IntervalBTree.<Item, 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
diff --git a/test/unit/org/apache/cassandra/utils/btree/IntervalBTreeTest.java
b/test/unit/org/apache/cassandra/utils/btree/IntervalBTreeTest.java
index 421e3a037f..7a421e13d5 100644
--- a/test/unit/org/apache/cassandra/utils/btree/IntervalBTreeTest.java
+++ b/test/unit/org/apache/cassandra/utils/btree/IntervalBTreeTest.java
@@ -27,15 +27,28 @@ import java.util.TreeSet;
import org.junit.Test;
+import accord.utils.DefaultRandom;
import accord.utils.Invariants;
+import accord.utils.RandomSource;
+import accord.utils.SymmetricComparator;
+import org.apache.cassandra.config.CassandraRelevantProperties;
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;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.endWithStart;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithEnd;
+import static
org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithStart;
public class IntervalBTreeTest
{
+ static
+ {
+ // build deeper trees to explore more behaviours
+ CassandraRelevantProperties.BTREE_BRANCH_SHIFT.setInt(3);
+ }
+
static class TestInterval implements Comparable<TestInterval>
{
final int start, end;
@@ -68,97 +81,118 @@ public class IntervalBTreeTest
{
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);
- }
+ @Override public Comparator<TestInterval> totalOrder() { return
TestInterval::compareTo; }
+ @Override public SymmetricComparator<TestInterval> endWithEndSorter()
{ return (a, b) -> Integer.compare(a.end, b.end); }
+ @Override public SymmetricComparator<TestInterval>
startWithStartSeeker() { return (a, b) ->
startWithStart(Integer.compare(a.start, b.start)); }
+ @Override public SymmetricComparator<TestInterval>
startWithEndSeeker() { return (a, b) -> startWithEnd(Integer.compare(a.start,
b.end)); }
+ @Override public SymmetricComparator<TestInterval>
endWithStartSeeker() { return (a, b) -> endWithStart(Integer.compare(a.end,
b.start)); }
}
@Test
public void testN()
{
-// testOne(4391017837511000309L);
+ testOne(7817231705170378212L);
Random seeds = new Random();
- for (int i = 0 ; i < 1000 ; ++i)
+ for (int i = 0 ; i < 200 ; ++i)
testOne(seeds.nextLong());
}
+ // TODO (expected): test extreme patterns
public static void testOne(long seed)
{
try
{
+ List<TestInterval> tmp = new ArrayList<>();
List<TestInterval> list = new ArrayList<>();
- Random random = new Random();
+ RandomSource random = new DefaultRandom();
random.setSeed(seed);
System.out.println(seed);
- int count = 1 << random.nextInt(11);
- int maxRemoveSize = count == 1 ? 1 : 1 + random.nextInt(count - 1);
+ int count = random.nextFloat() < 0.001 ? random.nextInt(1, 4) : 4
<< random.nextInt(10);
+ int keyDomain = count == 1 ? 2 : 1 + log2uniform(random, count);
+ int valueDomain = count == 1 ? 1 :
random.nextInt((int)Math.sqrt(count), count);
+ int maxRemoveSize = log2uniform(random, count);
+ int maxRunLength = log2uniform(random, count);
+ float runChance = random.nextFloat();
count = count + random.nextInt(count);
TreeSet<TestInterval> unique = new TreeSet<>();
for (int i = 0 ; i < count ; ++i)
{
- TestInterval interval = newInterval(random, 0, 10000);
+ TestInterval interval = newInterval(random, keyDomain,
valueDomain);
if (unique.add(interval))
list.add(interval);
+
+ if (maxRunLength > 1 && random.decide(runChance) && count - i
> 1)
+ {
+ boolean startRun = random.nextBoolean();
+ int runLength = random.nextBoolean() ? log2uniform(random,
maxRunLength - 1)
+ : random.nextInt(0,
maxRunLength - 1);
+ runLength = Math.min(runLength, count - (i + 1));
+ runLength = Math.min(runLength, startRun ? keyDomain - (1
+ interval.start) : interval.end);
+ while (--runLength >= 0)
+ {
+ int start = startRun ? interval.start :
random.nextInt(interval.end);
+ int end = startRun ? random.nextInt(interval.start +
1, keyDomain) : interval.end;
+ int value = random.nextInt(valueDomain);
+ TestInterval interval2 = new TestInterval(start, end,
value);
+ if (unique.add(interval2))
+ {
+ list.add(interval2);
+ ++i;
+ }
+ }
+ }
}
Object[] tree = BTree.empty();
- for (TestInterval v : list)
+ if (random.nextBoolean())
+ {
+ for (TestInterval v : list)
+ {
+ tree = IntervalBTree.update(tree, BTree.singleton(v),
TestComparators.INSTANCE);
+ }
+ }
+ else
{
- tree = IntervalBTree.update(tree, BTree.singleton(v),
TestComparators.INSTANCE);
+ tmp.addAll(list);
+ tmp.sort(TestComparators.INSTANCE.totalOrder());
+ try (BTree.FastBuilder<TestInterval> builder =
IntervalBTree.fastBuilder(TestComparators.INSTANCE))
+ {
+ for (TestInterval add : tmp)
+ builder.add(add);
+ tree = builder.build();
+ }
}
+ validate(tree, TestComparators.INSTANCE.endWithEndSorter());
- for (int i1 = 0 ; i1 < list.size() ; ++i1)
+ for (int i = 0 ; i < list.size() ; ++i)
{
- TestInterval iv = list.get(i1);
+ TestInterval iv = list.get(i);
TreeSet<TestInterval> collect = collect(list, 0, iv);
remove(tree, collect, iv);
Invariants.require(collect.isEmpty());
- iv = newInterval(random, 0, 10000);
+ iv = newInterval(random, keyDomain, valueDomain);
collect = collect(list, 0, iv);
remove(tree, collect, iv);
Invariants.require(collect.isEmpty());
}
- Collections.shuffle(list, random);
+ Collections.shuffle(list, random.asJdkRandom());
for (int i = 0 ; i < list.size() ;)
{
int remaining = list.size() - i;
- int c = remaining == 1 ? 1 : 1 +
random.nextInt(Math.min(remaining, maxRemoveSize));
+ int removeCount = remaining == 1 ? 1 : 1 +
random.nextInt(Math.min(remaining, maxRemoveSize));
+ remaining -= removeCount;
TestInterval iv = list.get(i++);
Object[] remove;
{
- remove = IntervalBTree.singleton(iv);
+ tmp.clear();
+ tmp.add(iv);
+ int c = removeCount;
while (--c > 0)
- remove = IntervalBTree.update(remove,
IntervalBTree.singleton(list.get(i++)), TestComparators.INSTANCE);
+ tmp.add(list.get(i++));
int notPresentCount;
switch (random.nextInt(4))
@@ -172,17 +206,25 @@ public class IntervalBTreeTest
while (--notPresentCount > 0)
{
- TestInterval add = newInterval(random, 0, 10000);
+ TestInterval add = newInterval(random, keyDomain,
valueDomain);
if (!unique.contains(add))
- remove = IntervalBTree.update(remove,
IntervalBTree.singleton(add), TestComparators.INSTANCE);
+ tmp.add(add);
+ }
+
+ tmp.sort(TestComparators.INSTANCE.totalOrder());
+ try (BTree.FastBuilder<TestInterval> builder =
IntervalBTree.fastBuilder(TestComparators.INSTANCE))
+ {
+ for (TestInterval add : tmp)
+ builder.add(add);
+ remove = builder.build();
}
}
tree = IntervalBTree.subtract(tree, remove,
TestComparators.INSTANCE);
- validate(tree,
TestComparators.INSTANCE.endWithEndComparator());
+ validate(tree, TestComparators.INSTANCE.endWithEndSorter());
TreeSet<TestInterval> collect = collect(list, i, iv);
remove(tree, collect, iv);
Invariants.require(collect.isEmpty());
- iv = newInterval(random, 0, 10000);
+ iv = newInterval(random, keyDomain, valueDomain);
collect = collect(list, i, iv);
remove(tree, collect, iv);
Invariants.require(collect.isEmpty());
@@ -194,6 +236,16 @@ public class IntervalBTreeTest
}
}
+ private static int log2uniform(RandomSource random, int max)
+ {
+ int logn = 31 - Integer.numberOfLeadingZeros(max);
+ int loglogn = 31 - Integer.numberOfLeadingZeros(logn);
+ int scale = 1 << random.nextInt(loglogn, logn);
+ if (scale == max)
+ return max;
+ return random.nextInt(scale, Math.min(scale * 2, max));
+ }
+
private static TreeSet<TestInterval> collect(List<TestInterval> list, int
from, TestInterval intersects)
{
TreeSet<TestInterval> collect = new TreeSet<>();
@@ -214,11 +266,11 @@ public class IntervalBTreeTest
}, removeFrom, null, null);
}
- private static TestInterval newInterval(Random random, int from, int to)
+ private static TestInterval newInterval(RandomSource random, int
keyDomain, int valueDomain)
{
- int end = 1 + from + random.nextInt(to - (1 + from));
- int start = from + random.nextInt(end - from);
- return new TestInterval(start, end, random.nextInt(10000));
+ int end = 1 + random.nextInt(keyDomain - 1);
+ int start = random.nextInt(end);
+ return new TestInterval(start, end, random.nextInt(valueDomain));
}
static Object validate(Object[] tree, Comparator endSorter)
@@ -235,6 +287,8 @@ public class IntervalBTreeTest
for (int i = 0 ; i < getChildCount(tree) ; ++i)
{
Object childMax = validate((Object[])tree[getChildStart(tree) +
i], endSorter);
+ if (i < getChildStart(tree) && endSorter.compare(childMax,
tree[i]) < 0)
+ childMax = tree[i];
if (endSorter.compare(childMax, max) > 0)
max = childMax;
Invariants.require(endSorter.compare(childMax, tmp[i]) == 0);
@@ -252,5 +306,4 @@ public class IntervalBTreeTest
}
return max;
}
-
}
diff --git
a/test/unit/org/apache/cassandra/utils/btree/IntervalTreePerfTest.java
b/test/unit/org/apache/cassandra/utils/btree/IntervalTreePerfTest.java
new file mode 100644
index 0000000000..db32a8acd8
--- /dev/null
+++ b/test/unit/org/apache/cassandra/utils/btree/IntervalTreePerfTest.java
@@ -0,0 +1,239 @@
+///*
+// * 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.nio.ByteBuffer;
+//import java.util.ArrayList;
+//import java.util.Collections;
+//import java.util.HashSet;
+//import java.util.List;
+//import java.util.Random;
+//import java.util.concurrent.ThreadLocalRandom;
+//
+//import com.google.common.base.Stopwatch;
+//import org.junit.Test;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import org.apache.cassandra.db.BufferDecoratedKey;
+//import org.apache.cassandra.db.DecoratedKey;
+//import org.apache.cassandra.db.PartitionPosition;
+//import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
+//import org.apache.cassandra.dht.Token;
+//import org.apache.cassandra.utils.Interval;
+//import org.apache.cassandra.utils.IntervalTree;
+//
+//import static java.lang.String.format;
+//import static java.util.concurrent.TimeUnit.NANOSECONDS;
+//
+//public class IntervalTreePerfTest
+//{
+// private static final Logger logger =
LoggerFactory.getLogger(IntervalTreePerfTest.class);
+//
+// private static final byte[] bigArray = new byte[1024 * 1024 * 64];
+//
+// static
+// {
+// Random r = new Random();
+// r.nextBytes(bigArray);
+// }
+//
+//
+// private static List<Interval<PartitionPosition, Integer>>
randomIntervals(int range, int increment, int count)
+// {
+// List<PartitionPosition> a = random(range, increment, count);
+// List<PartitionPosition> b = random(range, increment, count);
+// List<Interval<PartitionPosition, Integer>> r = new ArrayList<>();
+// for (int i = 0 ; i < count ; i++)
+// {
+// r.add(a.get(i).compareTo(b.get(i)) < 0 ?
Interval.create(a.get(i), b.get(i), i)
+// : Interval.create(b.get(i), a.get(i),
i));
+// }
+// return r;
+// }
+//
+// private static List<PartitionPosition> random(int range, int increment,
int count)
+// {
+// List<PartitionPosition> random = new ArrayList<>();
+// for (int i = 0 ; i < count ; i++)
+// {
+// int base = i * increment;
+// Token token = new
LongToken(ThreadLocalRandom.current().nextInt(base, base + range));
+// ByteBuffer key = ByteBuffer.allocate(16);
+//
key.putLong(token.getLongValue()).putLong(token.getLongValue()).flip();
+// DecoratedKey dk = new BufferDecoratedKey(token, key);
+// random.add(dk);
+// }
+// return random;
+// }
+//
+// @Test
+// public void test()
+// {
+// long checksum = 0;
+// int iterations = 1000;
+// int totalTableCount = 100000;
+// int additionalIntervalsCount = 1000;
+// long rangePersstable = Long.MAX_VALUE / 5;
+// int range = 1000000;
+//
+// List<Interval<PartitionPosition, Integer>> randomIntervals = new
ArrayList<>();
+// List<Interval<PartitionPosition, Integer>> additionalIntervals = new
ArrayList<>();
+// List<Interval<PartitionPosition, Integer>> insertIntervals =
randomIntervals;
+// for (int level = 0; level <= 7; level++)
+// {
+// for (int i = 0; i < (Long.MAX_VALUE / rangePersstable) * 2; i++)
+// {
+// long start = Long.MIN_VALUE + (rangePersstable * i);
+// if (randomIntervals.size() == totalTableCount)
+// insertIntervals = additionalIntervals;
+// if (additionalIntervals.size() == additionalIntervalsCount)
+// break;
+// Token startToken = new LongToken(start);
+// ByteBuffer startKey = ByteBuffer.allocate(16);
+// startKey.putLong(start).putLong(start).flip();
+// DecoratedKey startDK = new BufferDecoratedKey(startToken,
startKey);
+// Token endToken = new LongToken(start + rangePersstable);
+// ByteBuffer endKey = ByteBuffer.allocate(16);
+// endKey.putLong(start + rangePersstable).putLong(start +
rangePersstable).flip();
+// DecoratedKey endDK = new BufferDecoratedKey(endToken,
endKey);
+// insertIntervals.add(new Interval<>(startDK, endDK, null));
+// }
+// rangePersstable /= 10;
+// }
+// Collections.shuffle(randomIntervals);
+// Collections.shuffle(additionalIntervals);
+//
+// long min = Long.MAX_VALUE;
+// long max = Long.MIN_VALUE;
+// long total = 0;
+// logger.info("Testing interval tree 3 adding " +
additionalIntervals.size() + " intervals");
+// IntervalTree<PartitionPosition, Integer, Interval<PartitionPosition,
Integer>> tree3 = new IntervalTree<>(randomIntervals);
+// for (Interval<PartitionPosition, Integer> i : additionalIntervals)
+// {
+// for (byte b : bigArray)
+// checksum += b;
+// Stopwatch sw = Stopwatch.createStarted();
+// tree3.update(null, new Interval[] {i});
+// long elapsed = sw.elapsed(NANOSECONDS);
+// total += elapsed;
+// min = Math.min(min, elapsed);
+// max = Math.max (max, elapsed);
+// }
+// logger.info(format("tree 3 update average %.3f, min %.3f, max %.3f",
NANOSECONDS.toMicros(total / additionalIntervals.size()) / 1000.0,
NANOSECONDS.toMicros(min) / 1000.0, NANOSECONDS.toMicros(max) / 1000.0));
+//
+// logger.info("Testing leveled compaction building " +
randomIntervals.size() + " intervals");
+// long searchMin = Long.MAX_VALUE;
+// long searchMax = Long.MIN_VALUE;
+// long searchTotal = 0;
+// Token searchToken = new LongToken(0);
+// ByteBuffer searchKey =
ByteBuffer.allocate(16).putLong(0).putLong(0).flip();
+// DecoratedKey searchDK = new BufferDecoratedKey(searchToken,
searchKey);
+//
+// min = Long.MAX_VALUE;
+// max = Long.MIN_VALUE;
+// total = 0;
+// for (int i = 0 ; i < iterations; i++)
+// {
+// Stopwatch sw = Stopwatch.createStarted();
+// Object[] tree = buildBtree();
+// long elapsed = sw.elapsed(NANOSECONDS);
+// min = Math.min(min, elapsed);
+// max = Math.max(max, elapsed);
+// total += elapsed;
+// for (byte b : bigArray)
+// checksum += b;
+// sw = Stopwatch.createStarted();
+// List<Integer> result = IntervalBTree.<Object, Object, Object,
List<Integer>>accumulate(tree, , searchDK, (i1,i2,k,l) ->
{l.add((Integer)((Interval)k).data)}, null, null, new ArrayList<>());
+// long searchElapsed = sw.elapsed(NANOSECONDS);
+// searchMin = Math.min(searchMin, searchElapsed);
+// searchMax = Math.max(searchMax, searchElapsed);
+// searchTotal += searchElapsed;
+//// sw = Stopwatch.createStarted();
+//// tree.finish();
+//// System.out.println("Finishing took " +
sw.elapsed(TimeUnit.MILLISECONDS) + "ms");
+// }
+// logger.info(format("Legacy tree Average %.3f, min %.3f, max %.3f",
NANOSECONDS.toMicros(total / iterations) / 1000.0, NANOSECONDS.toMicros(min) /
1000.0, NANOSECONDS.toMicros(max) / 1000.0));
+// logger.info("Legacy tree search Average {}, min {}, max {}",
NANOSECONDS.toMicros(searchTotal / iterations),
NANOSECONDS.toMicros(searchMin), NANOSECONDS.toMicros(searchMax));
+//
+// logger.info("Testing random ranges");
+// randomIntervals = new ArrayList<>(new
HashSet<>(randomIntervals(range, 0, 100000)));
+//
+// min = Long.MAX_VALUE;
+// max = Long.MIN_VALUE;
+// total = 0;
+// logger.info("Testing interval tree 3 adding " +
additionalIntervals.size() + " intervals");
+// tree3 = new IntervalTree<>(randomIntervals);
+// for (Interval<PartitionPosition, Integer> i : additionalIntervals)
+// {
+// for (byte b : bigArray)
+// checksum += b;
+// Stopwatch sw = Stopwatch.createStarted();
+// tree3.update(null, new Interval[] {i});
+// long elapsed = sw.elapsed(NANOSECONDS);
+// total += elapsed;
+// min = Math.min(min, elapsed);
+// max = Math.max (max, elapsed);
+// }
+// logger.info(format("tree 3 update average %.3f, min %.3f, max %.3f",
NANOSECONDS.toMicros(total / additionalIntervals.size()) / 1000.0,
NANOSECONDS.toMicros(min) / 1000.0, NANOSECONDS.toMicros(max) / 1000.0));
+//
+// min = Long.MAX_VALUE;
+// max = Long.MIN_VALUE;
+// searchMin = Long.MAX_VALUE;
+// searchMax = Long.MIN_VALUE;
+// total = 0;
+// searchTotal = 0;
+// for (int i = 0 ; i < iterations; i++)
+// {
+// Stopwatch sw = Stopwatch.createStarted();
+// Object[] tree = buildBtree(randomIntervals)
+// IntervalTree<PartitionPosition, Integer,
Interval<PartitionPosition, Integer>> tree = new
LegacyIntervalTree<>(randomIntervals);
+// long elapsed = sw.elapsed(NANOSECONDS);
+// min = Math.min(min, elapsed);
+// max = Math.max(max, elapsed);
+// total += elapsed;
+// for (byte b : bigArray)
+// checksum += b;
+// sw = Stopwatch.createStarted();
+// List<Integer> result = tree.search(searchDK);
+// long searchElapsed = sw.elapsed(NANOSECONDS);
+// searchMin = Math.min(searchMin, searchElapsed);
+// searchMax = Math.max(searchMax, searchElapsed);
+// searchTotal += searchElapsed;
+//// sw = Stopwatch.createStarted();
+//// tree.finish();
+//// System.out.println("Finishing took " +
sw.elapsed(TimeUnit.MILLISECONDS) + "ms");
+// }
+// logger.info("Legacy tree Average {}, min {}, max {}",
NANOSECONDS.toMicros(total / iterations), NANOSECONDS.toMicros(min),
NANOSECONDS.toMicros(max));
+// logger.info("Checksum {}", checksum);
+// }
+//
+// private static Object[] buildBtree(List<Interval<PartitionPosition,
Integer>> intervals)
+// {
+// List<Interval<PartitionPosition, Integer>> sorted = new
ArrayList<>(intervals);
+// sorted.sort(Interval.minOrdering());
+// try (IntervalBTree.FastInteralTreeBuilder<Interval> builder =
(IntervalBTree.FastInteralTreeBuilder)
IntervalBTree.fastBuilder(Interval.maxOrdering()))
+// {
+// for (Interval interval : intervals)
+// builder.add(interval);
+// return builder.build();
+// }
+// }
+//}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]