DeltaTree improvements. DecodedNodes store non-parent ancestor pointer which enables moveNext() and movePrev() operations to be O(1) from any leaf node. This also enables seekLessThanOrEqual to accept a hint in the form of a cursor with a position close to the target to avoid traversal from the root when the destination is close to the hint. DeltaTree T::compare() now must take a skipLen argument which is used by insert() and seek operations to skip over bytes known to be shared between the new item or query and every node in the tree.

This commit is contained in:
Steve Atherton 2020-01-31 00:32:48 -08:00
parent 545a12533a
commit 460c9b78d8
2 changed files with 472 additions and 153 deletions

View File

@ -120,7 +120,8 @@ static int perfectSubtreeSplitPointCached(int subtree_size) {
// void writeDelta(dT &d, const T &base, int commonPrefix = -1) const;
//
// // Compare *this to t, returns < 0 for less than, 0 for equal, > 0 for greater than
// int compare(const T &rhs) const;
// // The first skipLen bytes can be assumed to be equal
// int compare(const T &rhs, int skipLen) const;
//
// // Get the common prefix bytes between *this and base
// // skip is a hint of how many prefix bytes are already known to be the same
@ -211,35 +212,39 @@ public:
}
struct DecodedNode {
// construct root node
DecodedNode(Node *raw, const T *prev, const T *next, Arena &arena)
: raw(raw), parent(nullptr), left(nullptr), right(nullptr), prev(prev), next(next),
: raw(raw), parent(nullptr), otherAncestor(nullptr), leftChild(nullptr), rightChild(nullptr), prev(prev), next(next),
item(raw->delta().apply(raw->delta().getPrefixSource() ? *prev : *next, arena))
{
//printf("DecodedNode1 raw=%p delta=%s\n", raw, raw->delta().toString().c_str());
}
DecodedNode(Node *raw, DecodedNode *parent, bool left, Arena &arena)
: parent(parent), raw(raw), left(nullptr), right(nullptr),
prev(left ? parent->prev : &parent->item),
next(left ? &parent->item : parent->next),
item(raw->delta().apply(raw->delta().getPrefixSource() ? *prev : *next, arena))
// Construct non-root node
// wentLeft indicates that we've gone left to get to the raw node.
DecodedNode(Node *raw, DecodedNode *parent, bool wentLeft, Arena &arena)
: parent(parent), otherAncestor(wentLeft ? parent->getPrevAncestor() : parent->getNextAncestor()),
prev(wentLeft ? parent->prev : &parent->item),
next(wentLeft ? &parent->item : parent->next),
leftChild(nullptr), rightChild(nullptr),
raw(raw), item(raw->delta().apply(raw->delta().getPrefixSource() ? *prev : *next, arena))
{
//printf("DecodedNode2 raw=%p delta=%s\n", raw, raw->delta().toString().c_str());
}
// Add newItem to tree and create a DecodedNode for it, linked to parent via the left or right child link
DecodedNode(DeltaTree *tree, const T &newItem, DecodedNode *parent, bool left, Arena &arena)
: parent(parent), raw(&tree->newNode()), left(nullptr), right(nullptr),
prev(left ? parent->prev : &parent->item),
next(left ? &parent->item : parent->next),
item(arena, newItem)
DecodedNode(DeltaTree *tree, const T &newItem, int skipLen, DecodedNode *parent, bool wentLeft, Arena &arena)
: parent(parent), otherAncestor(wentLeft ? parent->getPrevAncestor() : parent->getNextAncestor()),
prev(wentLeft ? parent->prev : &parent->item),
next(wentLeft ? &parent->item : parent->next),
leftChild(nullptr), rightChild(nullptr),
raw(&tree->newNode())
{
raw->leftChildOffset = 0;
raw->rightChildOffset = 0;
// TODO: Get subtreeCommon in here somehow.
int commonWithPrev = newItem.getCommonPrefixLen(*prev, 0);
int commonWithNext = newItem.getCommonPrefixLen(*next, 0);
int commonWithPrev = newItem.getCommonPrefixLen(*prev, skipLen);
int commonWithNext = newItem.getCommonPrefixLen(*next, skipLen);
bool prefixSourcePrev;
int commonPrefix;
@ -257,36 +262,82 @@ public:
int deltaSize = newItem.writeDelta(raw->delta(), *base, commonPrefix);
raw->delta().setPrefixSource(prefixSourcePrev);
item = raw->delta().apply(*base, arena);
tree->nodeBytes += sizeof(Node) + deltaSize;
++tree->numItems;
}
Node *raw;
DecodedNode *parent;
DecodedNode *left;
DecodedNode *right;
const T *prev; // greatest ancestor to the left
const T *next; // least ancestor to the right
T item;
DecodedNode *getRight(Arena &arena) {
if(right == nullptr) {
Node *n = raw->rightChild();
if(n != nullptr) {
right = new (arena) DecodedNode(n, this, false, arena);
}
}
return right;
// Returns true if otherAncestor is the previous ("greatest lesser") ancestor
bool otherAncestorPrev() const {
return parent && parent->leftChild == this;
}
DecodedNode *getLeft(Arena &arena) {
if(left == nullptr) {
Node *n = raw->leftChild();
// Returns true if otherAncestor is the next ("least greator") ancestor
bool otherAncestorNext() const {
return parent && parent->rightChild == this;
}
DecodedNode * getPrevAncestor() const {
return otherAncestorPrev() ? otherAncestor : parent;
}
DecodedNode * getNextAncestor() const {
return otherAncestorNext() ? otherAncestor : parent;
}
DecodedNode * jumpNext(DecodedNode *root) const {
if(otherAncestorNext()) {
return (otherAncestor != nullptr) ? otherAncestor : rightChild;
}
else {
if(this == root) {
return rightChild;
}
return (otherAncestor != nullptr) ? otherAncestor->rightChild : root;
}
}
DecodedNode * jumpPrev(DecodedNode *root) const {
if(otherAncestorPrev()) {
return (otherAncestor != nullptr) ? otherAncestor : leftChild;
}
else {
if(this == root) {
return leftChild;
}
return (otherAncestor != nullptr) ? otherAncestor->leftChild : root;
}
}
Node *raw;
DecodedNode *parent;
DecodedNode *otherAncestor;
DecodedNode *leftChild;
DecodedNode *rightChild;
const T *prev; // greatest ancestor to the left, or tree lower bound
const T *next; // least ancestor to the right, or tree upper bound
T item;
DecodedNode *getRightChild(Arena &arena) {
if(rightChild == nullptr) {
Node *n = raw->rightChild();
if(n != nullptr) {
left = new (arena) DecodedNode(n, this, true, arena);
rightChild = new (arena) DecodedNode(n, this, false, arena);
}
}
return left;
return rightChild;
}
DecodedNode *getLeftChild(Arena &arena) {
if(leftChild == nullptr) {
Node *n = raw->leftChild();
if(n != nullptr) {
leftChild = new (arena) DecodedNode(n, this, true, arena);
}
}
return leftChild;
}
};
@ -298,9 +349,9 @@ public:
// on the behavior of T::Delta::apply())
struct Mirror : FastAllocated<Mirror> {
Mirror(const void *treePtr = nullptr, const T *lowerBound = nullptr, const T *upperBound = nullptr)
: tree((DeltaTree *)treePtr), lower(lowerBound), upper(upperBound) {
// TODO: Remove these copies into arena and require users of Reader to keep prev and next alive during its lifetime
: tree((DeltaTree *)treePtr), lower(lowerBound), upper(upperBound)
{
// TODO: Remove these copies into arena and require users of Mirror to keep prev and next alive during its lifetime
lower = new(arena) T(arena, *lower);
upper = new(arena) T(arena, *upper);
@ -327,22 +378,22 @@ public:
// Insert k into the DeltaTree, updating nodeBytes and initialHeight.
// It's up to the caller to know that it will fit in the space available.
void insert(const T &k) {
void insert(const T &k, int skipLen = 0) {
int height = 1;
DecodedNode *n = root;
while(n != nullptr) {
int cmp = k.compare(n->item);
int cmp = k.compare(n->item, skipLen);
if(cmp >= 0) {
DecodedNode *right = n->getRight(arena);
DecodedNode *right = n->getRightChild(arena);
if(right == nullptr) {
// Set the right child of the decoded node to a new decoded node that points to a newly
// allocated/written raw node in the tree. DecodedNode() will write the new node
// and update nodeBytes
n->right = new (arena) DecodedNode(tree, k, n, false, arena);
n->raw->rightChildOffset = (uint8_t *)n->right->raw - (uint8_t *)n->raw;
n->rightChild = new (arena) DecodedNode(tree, k, skipLen, n, false, arena);
n->raw->rightChildOffset = (uint8_t *)n->rightChild->raw - (uint8_t *)n->raw;
//printf("inserted %s at offset %d\n", k.toString().c_str(), n->raw->rightChildOffset);
// Update max height of the tree if necessary
@ -356,12 +407,12 @@ public:
n = right;
}
else {
DecodedNode *left = n->getLeft(arena);
DecodedNode *left = n->getLeftChild(arena);
if(left == nullptr) {
// See right side case above for comments
n->left = new (arena) DecodedNode(tree, k, n, true, arena);
n->raw->leftChildOffset = (uint8_t *)n->left->raw - (uint8_t *)n->raw;
n->leftChild = new (arena) DecodedNode(tree, k, skipLen, n, true, arena);
n->raw->leftChildOffset = (uint8_t *)n->leftChild->raw - (uint8_t *)n->raw;
//printf("inserted %s at offset %d\n", k.toString().c_str(), n->raw->leftChildOffset);
if(height > tree->maxHeight) {
@ -379,15 +430,15 @@ public:
};
// Cursor provides a way to seek into a DeltaTree and iterate over its contents
// All Cursors from a Reader share the same decoded node 'cache' (tree of DecodedNodes)
// All Cursors from a Mirror share the same decoded node 'cache' (tree of DecodedNodes)
struct Cursor {
Cursor() : reader(nullptr), node(nullptr) {
Cursor() : mirror(nullptr), node(nullptr) {
}
Cursor(Mirror *r) : reader(r), node(reader->root) {
Cursor(Mirror *r) : mirror(r), node(mirror->root) {
}
Mirror *reader;
Mirror *mirror;
DecodedNode *node;
bool valid() const {
@ -399,7 +450,7 @@ public:
}
const T & getOrUpperBound() const {
return valid() ? node->item : *reader->upperBound();
return valid() ? node->item : *mirror->upperBound();
}
bool operator==(const Cursor &rhs) const {
@ -410,28 +461,118 @@ public:
return node != rhs.node;
}
bool seekLessThanOrEqual(const T &s, int skipLen = 0) {
return seekLessThanOrEqual(s, skipLen, nullptr, 0);
}
bool seekLessThanOrEqual(const T &s, int skipLen, const Cursor *pHint) {
if(pHint->valid()) {
return seekLessThanOrEqual(s, skipLen, pHint, s.compare(pHint->get(), skipLen));
}
return seekLessThanOrEqual(s, skipLen, nullptr, 0);
}
// Moves the cursor to the node with the greatest key less than or equal to s. If successful,
// returns true, otherwise returns false and the cursor will be at the node with the next key
// greater than s.
bool seekLessThanOrEqual(const T &s) {
node = nullptr;
DecodedNode *n = reader->root;
// returns true, otherwise returns false and the cursor position will be invalid.
// If pHint is given then initialCmp must be logically equivalent to s.compare(pHint->get())
// If hintFwd is omitted, it will be calculated (see other definitions above)
bool seekLessThanOrEqual(const T &s, int skipLen, const Cursor *pHint, int initialCmp) {
DecodedNode *n;
while(n != nullptr) {
int cmp = s.compare(n->item);
if(cmp == 0) {
// If there's a hint position, use it
// At the end of using the hint, if n is valid it should point to a node which has not yet been compared to.
if(pHint != nullptr && pHint->node != nullptr) {
n = pHint->node;
if(initialCmp == 0) {
node = n;
return true;
}
if(initialCmp > 0) {
node = n;
while(n != nullptr) {
n = n->jumpNext(mirror->root);
if(n == nullptr) {
break;
}
if(cmp < 0) {
n = n->getLeft(reader->arena);
int cmp = s.compare(n->item, skipLen);
if(cmp > 0) {
node = n;
continue;
}
if(cmp == 0) {
node = n;
n = nullptr;
}
else {
n = n->leftChild;
}
break;
}
}
else {
// n < s so store it in node as a potential result
while(n != nullptr) {
n = n->jumpPrev(mirror->root);
if(n == nullptr) {
break;
}
int cmp = s.compare(n->item, skipLen);
if(cmp >= 0) {
node = n;
n = (cmp == 0) ? nullptr : n->rightChild;
break;
}
}
}
}
else {
// Start at root, clear current position
n = mirror->root;
node = nullptr;
}
while(n != nullptr) {
int cmp = s.compare(n->item, skipLen);
if(cmp < 0) {
n = n->getLeftChild(mirror->arena);
}
else {
// n <= s so store it in node as a potential result
node = n;
n = n->getRight(reader->arena);
if(cmp == 0) {
return true;
}
n = n->getRightChild(mirror->arena);
}
}
return node != nullptr;
}
// Moves the cursor to the node with the lowest key greater than or equal to s. If successful,
// returns true, otherwise returns false and the cursor position will be invalid.
bool seekGreaterThanOrEqual(const T &s, int skipLen = 0) {
DecodedNode *n = mirror->root;
node = nullptr;
while(n != nullptr) {
int cmp = s.compare(n->item, skipLen);
if(cmp > 0) {
n = n->getRightChild(mirror->arena);
}
else {
// n >= s so store it in node as a potential result
node = n;
if(cmp == 0) {
return true;
}
n = n->getLeftChild(mirror->arena);
}
}
@ -439,10 +580,10 @@ public:
}
bool moveFirst() {
DecodedNode *n = reader->root;
DecodedNode *n = mirror->root;
node = n;
while(n != nullptr) {
n = n->getLeft(reader->arena);
n = n->getLeftChild(mirror->arena);
if(n != nullptr)
node = n;
}
@ -450,10 +591,10 @@ public:
}
bool moveLast() {
DecodedNode *n = reader->root;
DecodedNode *n = mirror->root;
node = n;
while(n != nullptr) {
n = n->getRight(reader->arena);
n = n->getRightChild(mirror->arena);
if(n != nullptr)
node = n;
}
@ -462,52 +603,38 @@ public:
bool moveNext() {
// Try to go right
DecodedNode *n = node->getRight(reader->arena);
if(n != nullptr) {
// Go left as far as possible
while(n != nullptr) {
node = n;
n = n->getLeft(reader->arena);
}
return true;
DecodedNode *n = node->getRightChild(mirror->arena);
// If we couldn't go right, then the answer is our next ancestor
if(n == nullptr) {
node = node->getNextAncestor();
return node != nullptr;
}
// Follow parent links until a greater parent is found
while(node->parent != nullptr) {
bool greaterParent = node->parent->left == node;
node = node->parent;
if(greaterParent) {
return true;
}
// Go left as far as possible
while(n != nullptr) {
node = n;
n = n->getLeftChild(mirror->arena);
}
node = nullptr;
return false;
return true;
}
bool movePrev() {
// Try to go left
DecodedNode *n = node->getLeft(reader->arena);
if(n != nullptr) {
// Go right as far as possible
while(n != nullptr) {
node = n;
n = n->getRight(reader->arena);
}
return true;
DecodedNode *n = node->getLeftChild(mirror->arena);
// If we couldn't go left, then the answer is our prev ancestor
if(n == nullptr) {
node = node->getPrevAncestor();
return node != nullptr;
}
// Follow parent links until a lesser parent is found
while(node->parent != nullptr) {
bool lesserParent = node->parent->right == node;
node = node->parent;
if(lesserParent) {
return true;
}
// Go right as far as possible
while(n != nullptr) {
node = n;
n = n->getRightChild(mirror->arena);
}
node = nullptr;
return false;
return true;
}
};

View File

@ -1995,7 +1995,7 @@ struct RedwoodRecordRef {
// Find the common prefix between two records, assuming that the first
// skip bytes are the same.
inline int getCommonPrefixLen(const RedwoodRecordRef &other, int skip) const {
inline int getCommonPrefixLen(const RedwoodRecordRef &other, int skip = 0) const {
int skipStart = std::min(skip, key.size());
int common = skipStart + commonPrefixLength(key.begin() + skipStart, other.key.begin() + skipStart, std::min(other.key.size(), key.size()) - skipStart);
@ -2006,6 +2006,28 @@ struct RedwoodRecordRef {
return common;
}
// Compares and orders by key, version, chunk.start, chunk.total.
// Value is not considered, as it is does not make sense for a container
// to have two records which differ only in value.
int compare(const RedwoodRecordRef &rhs, int skip = 0) const {
int keySkip = std::min(skip, key.size());
int cmp = key.substr(keySkip).compare(rhs.key.substr(keySkip));
if(cmp == 0) {
cmp = version - rhs.version;
if(cmp == 0) {
// It is assumed that in any data set there will never be more than one
// unique chunk total size for the same key and version, so sort by start, total
// Chunked (represented by chunk.total > 0) sorts higher than whole
cmp = chunk.start - rhs.chunk.start;
if(cmp == 0) {
cmp = chunk.total - rhs.chunk.total;
}
}
}
return cmp;
}
static const int intFieldArraySize = 14;
// Write big endian values of version (64 bits), total (24 bits), and start (24 bits) fields
@ -2289,26 +2311,6 @@ struct RedwoodRecordRef {
};
#pragma pack(pop)
// Compares and orders by key, version, chunk.start, chunk.total.
// Value is not considered, as it is does not make sense for a container
// to have two records which differ only in value.
int compare(const RedwoodRecordRef &rhs) const {
int cmp = key.compare(rhs.key);
if(cmp == 0) {
cmp = version - rhs.version;
if(cmp == 0) {
// It is assumed that in any data set there will never be more than one
// unique chunk total size for the same key and version, so sort by start, total
// Chunked (represented by chunk.total > 0) sorts higher than whole
cmp = chunk.start - rhs.chunk.start;
if(cmp == 0) {
cmp = chunk.total - rhs.chunk.total;
}
}
}
return cmp;
}
// Compares key fields and value for equality
bool identical(const RedwoodRecordRef &rhs) const {
return compare(rhs) == 0 && value == rhs.value;
@ -5314,9 +5316,23 @@ struct IntIntPair {
}
};
int compare(const IntIntPair &rhs) const {
//printf("compare %s to %s\n", toString().c_str(), rhs.toString().c_str());
int cmp = k - rhs.k;
// For IntIntPair, skipLen will be in units of fields, not bytes
int getCommonPrefixLen(const IntIntPair &other, int skip = 0) const {
if(k == other.k) {
if(v == other.v) {
return 2;
}
return 1;
}
return 0;
}
int compare(const IntIntPair &rhs, int skip = 0) const {
if(skip == 2) {
return 0;
}
int cmp = (skip > 0) ? 0 : (k - rhs.k);
if(cmp == 0) {
cmp = v - rhs.v;
}
@ -5324,17 +5340,13 @@ struct IntIntPair {
}
bool operator==(const IntIntPair &rhs) const {
return k == rhs.k;
return compare(rhs) == 0;
}
bool operator<(const IntIntPair &rhs) const {
return compare(rhs) < 0;
}
int getCommonPrefixLen(const IntIntPair &other, int skip) const {
return 0;
}
int deltaSize(const IntIntPair &base) const {
return sizeof(Delta);
}
@ -5701,11 +5713,11 @@ TEST_CASE("!/redwood/correctness/unit/deltaTree/RedwoodRecordRef") {
TEST_CASE("!/redwood/correctness/unit/deltaTree/IntIntPair") {
const int N = 200;
IntIntPair prev = {0, 0};
IntIntPair next = {1000, 0};
IntIntPair prev = {1, 0};
IntIntPair next = {10000, 10000};
state std::function<IntIntPair()> randomPair = []() {
return IntIntPair({deterministicRandom()->randomInt(0, 1000), deterministicRandom()->randomInt(0, 1000)});
state std::function<IntIntPair()> randomPair = [&]() {
return IntIntPair({deterministicRandom()->randomInt(prev.k, next.k), deterministicRandom()->randomInt(prev.v, next.v)});
};
// Build a sorted vector of N items
@ -5736,41 +5748,221 @@ TEST_CASE("!/redwood/correctness/unit/deltaTree/IntIntPair") {
}
std::sort(items.begin(), items.end());
auto printItems = [&] {
for(int k = 0; k < items.size(); ++k) {
printf("%d %s\n", k, items[k].toString().c_str());
}
};
printf("Count=%d Size=%d InitialHeight=%d MaxHeight=%d\n", (int)items.size(), (int)tree->size(), (int)tree->initialHeight, (int)tree->maxHeight);
debug_printf("Data(%p): %s\n", tree, StringRef((uint8_t *)tree, tree->size()).toHexString().c_str());
// Iterate through items and tree forward and backward, verifying tree contents.
auto scanAndVerify = [&]() {
ASSERT(fwd.moveFirst());
ASSERT(rev.moveLast());
for(int i = 0; i < items.size(); ++i) {
if(fwd.get() != items[i]) {
printItems();
printf("forward iterator i=%d\n %s found\n %s expected\n", i, fwd.get().toString().c_str(), items[i].toString().c_str());
ASSERT(false);
}
if(rev.get() != items[items.size() - 1 - i]) {
printItems();
printf("reverse iterator i=%d\n %s found\n %s expected\n", i, rev.get().toString().c_str(), items[items.size() - 1 - i].toString().c_str());
ASSERT(false);
}
// Advance iterator, check scanning cursors for correct validity state
int j = i + 1;
bool end = j == items.size();
ASSERT(fwd.moveNext() == !end);
ASSERT(rev.movePrev() == !end);
ASSERT(fwd.valid() == !end);
ASSERT(rev.valid() == !end);
if(end) {
break;
}
}
};
// Verify tree contents
scanAndVerify();
// Create a new mirror, decoding the tree from scratch since insert() modified both the tree and the mirror
r = DeltaTree<IntIntPair>::Mirror(tree, &prev, &next);
fwd = r.getCursor();
rev = r.getCursor();
// Verify tree contents again
scanAndVerify();
// Iterate through items and tree forward and backward, verifying tree contents.
ASSERT(fwd.moveFirst());
ASSERT(rev.moveLast());
int i = 0;
while(1) {
for(int i = 0; i < items.size(); ++i) {
if(fwd.get() != items[i]) {
printItems();
printf("forward iterator i=%d\n %s found\n %s expected\n", i, fwd.get().toString().c_str(), items[i].toString().c_str());
ASSERT(false);
}
if(rev.get() != items[items.size() - 1 - i]) {
printItems();
printf("reverse iterator i=%d\n %s found\n %s expected\n", i, rev.get().toString().c_str(), items[items.size() - 1 - i].toString().c_str());
ASSERT(false);
}
++i;
ASSERT(fwd.moveNext() == rev.movePrev());
ASSERT(fwd.valid() == rev.valid());
if(!fwd.valid()) {
// Advance iterator, check scanning cursors for correct validity state
int j = i + 1;
bool end = j == items.size();
ASSERT(fwd.moveNext() == !end);
ASSERT(rev.movePrev() == !end);
ASSERT(fwd.valid() == !end);
ASSERT(rev.valid() == !end);
if(end) {
break;
}
}
ASSERT(i == items.size());
DeltaTree<IntIntPair>::Cursor c = r.getCursor();
DeltaTree<IntIntPair>::Cursor s = r.getCursor();
double start = timer();
for(int i = 0; i < 20000000; ++i) {
IntIntPair &p = items[deterministicRandom()->randomInt(0, items.size())];
if(!c.seekLessThanOrEqual(p)) {
printf("Not found! query=%s\n", p.toString().c_str());
// SeekLTE to each element
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
ASSERT(s.seekLessThanOrEqual(q));
if(s.get() != p) {
printItems();
printf("seekLessThanOrEqual(%s) found %s expected %s\n", q.toString().c_str(), s.get().toString().c_str(), p.toString().c_str());
ASSERT(false);
}
if(c.get() != p) {
printf("Found incorrect node! query=%s found=%s\n", p.toString().c_str(), c.get().toString().c_str());
}
// SeekGTE to each element
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
ASSERT(s.seekGreaterThanOrEqual(q));
if(s.get() != p) {
printItems();
printf("seekGreaterThanOrEqual(%s) found %s expected %s\n", q.toString().c_str(), s.get().toString().c_str(), p.toString().c_str());
ASSERT(false);
}
}
// SeekLTE to the next possible int pair value after each element to make sure the base element is found
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
q.v++;
ASSERT(s.seekLessThanOrEqual(q));
if(s.get() != p) {
printItems();
printf("seekLessThanOrEqual(%s) found %s expected %s\n", q.toString().c_str(), s.get().toString().c_str(), p.toString().c_str());
ASSERT(false);
}
}
// SeekGTE to the previous possible int pair value after each element to make sure the base element is found
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
q.v--;
ASSERT(s.seekGreaterThanOrEqual(q));
if(s.get() != p) {
printItems();
printf("seekGreaterThanOrEqual(%s) found %s expected %s\n", q.toString().c_str(), s.get().toString().c_str(), p.toString().c_str());
ASSERT(false);
}
}
// SeekLTE to each element N times, using every element as a hint
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
for(int j = 0; j < items.size(); ++j) {
ASSERT(s.seekLessThanOrEqual(items[j]));
ASSERT(s.seekLessThanOrEqual(q, 0, &s));
if(s.get() != p) {
printItems();
printf("i=%d j=%d\n", i, j);
ASSERT(false);
}
}
}
// SeekLTE to each element's next possible value, using each element as a hint
for(int i = 0; i < items.size(); ++i) {
IntIntPair p = items[i];
IntIntPair q = p;
q.v++;
for(int j = 0; j < items.size(); ++j) {
ASSERT(s.seekLessThanOrEqual(items[j]));
ASSERT(s.seekLessThanOrEqual(q, 0, &s));
if(s.get() != p) {
printItems();
printf("i=%d j=%d\n", i, j);
ASSERT(false);
}
}
}
auto skipSeekPerformance = [&](int jumpMax, bool useHint, int count) {
// Skip to a series of increasing items, jump by up to jumpMax units forward in the
// items, wrapping around to 0.
double start = timer();
s.moveFirst();
auto first = s;
int pos = 0;
for(int c = 0; c < count; ++c) {
int jump = deterministicRandom()->randomInt(0, jumpMax);
int newPos = pos + jump;
if(newPos >= items.size()) {
pos = 0;
newPos = jump;
s = first;
}
IntIntPair q = items[newPos];
++q.v;
if(useHint) {
s.seekLessThanOrEqual(q, 0, &s, newPos - pos);
}
else {
s.seekLessThanOrEqual(q);
}
pos = newPos;
}
double elapsed = timer() - start;
printf("Seek/skip test, jumpMax=%d, items=%d, useHint=%d: Elapsed %f s\n", jumpMax, items.size(), useHint, elapsed);
};
skipSeekPerformance(10, false, 20e6);
skipSeekPerformance(10, true, 20e6);
// Repeatedly seek for one of a set of pregenerated random pairs and time it.
std::vector<IntIntPair> randomPairs;
for(int i = 0; i < 10 * N; ++i) {
randomPairs.push_back(randomPair());
}
double start = timer();
for(int i = 0; i < 200000000; ++i) {
IntIntPair p = randomPairs[i % randomPairs.size()];
// Verify the result is less than or equal, and if seek fails then p must be lower than lowest (first) item
if(!s.seekLessThanOrEqual(p)) {
if(p >= items.front()) {
printf("Seek failed! query=%s front=%s\n", p.toString().c_str(), items.front().toString().c_str());
ASSERT(false);
}
}
else if(s.get() > p) {
printf("Found incorrect node! query=%s found=%s\n", p.toString().c_str(), s.get().toString().c_str());
ASSERT(false);
}
}