foundationdb/fdbserver/PrefixTree.h

1050 lines
32 KiB
C++

/*
* PrefixTree.h
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed 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.
*/
#pragma once
#include "flow/flow.h"
#include "flow/Arena.h"
#include "fdbclient/FDBTypes.h"
#include "fdbserver/Knobs.h"
#include <string.h>
typedef uint64_t Word;
static inline int commonPrefixLength(uint8_t const* ap, uint8_t const* bp, int cl) {
int i = 0;
const int wordEnd = cl - sizeof(Word) + 1;
for(; i < wordEnd; i += sizeof(Word)) {
Word a = *(Word *)ap;
Word b = *(Word *)bp;
if(a != b) {
return i + ctzll(a ^ b) / 8;
}
ap += sizeof(Word);
bp += sizeof(Word);
}
for (; i < cl; i++) {
if (*ap != *bp) {
return i;
}
++ap;
++bp;
}
return cl;
}
static int commonPrefixLength(StringRef a, StringRef b) {
return commonPrefixLength(a.begin(), b.begin(), std::min(a.size(), b.size()));
}
// This appears to be the fastest version
static int lessOrEqualPowerOfTwo(int n) {
int p;
for (p = 1; p+p <= n; p+=p);
return p;
}
/*
static int _lessOrEqualPowerOfTwo(uint32_t n) {
if(n == 0)
return n;
int trailing = __builtin_ctz(n);
int leading = __builtin_clz(n);
if(trailing + leading == ((sizeof(n) * 8) - 1))
return n;
return 1 << ( (sizeof(n) * 8) - leading - 1);
}
static int __lessOrEqualPowerOfTwo(unsigned int n) {
int p = 1;
for(; p <= n; p <<= 1);
return p >> 1;
}
*/
static int perfectSubtreeSplitPoint(int subtree_size) {
// return the inorder index of the root node in a subtree of the given size
// consistent with the resulting binary search tree being "perfect" (having minimal height
// and all missing nodes as far right as possible).
// There has to be a simpler way to do this.
int s = lessOrEqualPowerOfTwo((subtree_size - 1) / 2 + 1) - 1;
return std::min(s * 2 + 1, subtree_size - s - 1);
}
static int perfectSubtreeSplitPointCached(int subtree_size) {
static uint16_t *points = nullptr;
static const int max = 500;
if(points == nullptr) {
points = new uint16_t[max];
for(int i = 0; i < max; ++i)
points[i] = perfectSubtreeSplitPoint(i);
}
if(subtree_size < max)
return points[subtree_size];
return perfectSubtreeSplitPoint(subtree_size);
}
struct PrefixTree {
// TODO: Make PrefixTree use a more complex record type with a multi column key
typedef KeyValueRef EntryRef;
typedef Standalone<EntryRef> Entry;
static int MaximumTreeSize() {
return std::numeric_limits<uint16_t>::max();
};
struct Node {
uint8_t flags;
/*
* Node fields
*
* Logically, a node has the following things
* - Flags describing what is in the node
* - Optional left child
* - Optional right child
* - Prefix string, described by a length and a source (which is the most recent left or right ancestor)
* - Optional split string, which contains any bytes after prefix which are needed to make a branching decision
* - Optional suffix string, containing any remaining key bytes after the split string
* - Optional value string
*
* The physical layout places the left child subtree immediately after the split string so that it is likely
* that the bytes read to make a branching decision and then choosing left (as should happen half of the time)
* will have a high cache hit rate.
*
* If necessary, the flags byte could be an enumeration into a set of possible options, since not all options
* combinations are needed. For example,
*
* - The tree is balanced and filled from the left at the last level, so a node cannot have only a right child.
* - If there are no children, there is no point in splitting any key bytes after the prefix into separate strings.
* - If there is exactly one child (left) then the key bytes after the prefix can all go in the split string. The
* traversal decision is to either stop or go left and one of those options (stop) will still have good memory
* locality.
*
* 8 valid/necessary option combinations for presense of (Left, Right, Split, Suffix) out of 16 possibilities
*
* L R Split Suffix
*
* N N N N # No children, key has no bytes after prefix
* N N Y N # No children, key has bytes after prefix
* Y N N N # One child, key has no bytes after prefix
* Y N Y N # One child, key has bytes after prefix
* Y Y N N # Two children, key has no bytes after prefix
* Y Y N Y # Two children, branch decision can be made using only prefix bytes but there are more key bytes after
* Y Y Y N # Two children, branch decision requires all key bytes after prefix
* Y Y Y Y # Two children, branch decision requires some but not all bytes after prefix
*
* This can be represent with just 3 bits, if necessary, but for now there is space in the flags byte for all 4.
*
* Flag Bits
*
* prefix borrow from next
* true - borrow from the closest ancestor greater than this node
* false - borrow from the closest ancestor less than this node
* large lengths = use 2 byte ints instead of 1 byte for prefix, split, suffix, and value lengths
* (TODO: It might be better to just not use a suffix at all when large is lengths is set)
* left child present
* right child present
* split string present
* suffix string present
* value string present
*
* Serialized format:
* All lengths are in the header, which has variable size
*
* flags 1 byte
* prefix length 1-2 bytes based on large lengths flag
* split length 0-2 bytes based on split string present flag
* suffix length 0-2 bytes based on suffix string present and large lengths flags
* value length 0-1 bytes based on value string present and large lengths flag
* left length 0 or 2 bytes depending on left child present
* split 0+ bytes
* left child 0+ bytes
* suffix 0+ bytes
* value 0+ bytes
* right child 0+ bytes
*
*/
enum EFlags {
USE_LARGE_LENGTHS = 1 << 0,
PREFIX_SOURCE_NEXT = 1 << 1,
HAS_LEFT_CHILD = 1 << 2,
HAS_RIGHT_CHILD = 1 << 3,
HAS_SPLIT = 1 << 4,
HAS_SUFFIX = 1 << 5,
HAS_VALUE = 1 << 6
};
// Stores decoded offsets (from beginning) of Node components
struct Parser {
Parser() {}
Parser(const Node *n) {
init(n);
}
const Node *node;
typedef uint16_t OffsetT;
OffsetT headerLen;
OffsetT prefixLen;
OffsetT leftPos;
OffsetT suffixPos;
OffsetT valuePos;
OffsetT rightPos;
StringRef splitString() const {
return StringRef((const uint8_t *)node + headerLen, leftPos);
}
StringRef suffixString() const {
return StringRef((const uint8_t *)node + headerLen + suffixPos, valuePos - suffixPos);
}
StringRef valueString() const {
return StringRef((const uint8_t *)node + headerLen + valuePos, rightPos - valuePos);
}
const Node *leftChild() const {
if(node->flags & HAS_LEFT_CHILD)
return (const Node *)((const uint8_t *)node + headerLen + leftPos);
return nullptr;
}
const Node *rightChild() const {
if(node->flags & HAS_RIGHT_CHILD)
return (const Node *)((const uint8_t *)node + headerLen + rightPos);
return nullptr;
}
int keyLen() const {
int len = prefixLen + leftPos + (valuePos - suffixPos);
ASSERT(len >= 0);
return len;
}
void init(const Node *n) {
node = n;
union {
const uint8_t *p8;
const uint16_t *p16;
};
p8 = (const uint8_t *)&n->flags + 1;
int flags = n->flags;
bool large = flags & USE_LARGE_LENGTHS;
prefixLen = large ? *p16++ : *p8++;
if(flags & HAS_SPLIT)
leftPos = large ? *p16++ : *p8++;
else
leftPos = 0;
suffixPos = leftPos;
if(flags & HAS_LEFT_CHILD)
suffixPos += *p16++;
valuePos = suffixPos;
if(flags & HAS_SUFFIX)
valuePos += (large ? *p16++ : *p8++);
rightPos = valuePos;
if(flags & HAS_VALUE)
rightPos += (large ? *p16++ : *p8++);
int header = 2; // flags byte, first prefix len byte
if(large)
++header; // second prefix len byte
if(flags & HAS_SPLIT)
header += large ? 2 : 1;
if(flags & HAS_LEFT_CHILD)
header += 2;
if(flags & HAS_SUFFIX)
header += large ? 2 : 1;
if(flags & HAS_VALUE)
header += large ? 2 : 1;
headerLen = header;
}
};
static inline int getMaxOverhead(int index, int keySize, int valueSize) {
bool large = keySize > 255 || valueSize > 255;
int overhead = 1 + (large ? 2 : 1); // flags and prefix len
// Value length size if present
if(valueSize > 0)
overhead += large ? 2 : 1;
overhead += large ? 6 : 3; // Worst case scenario for value, split and suffix lengths
if((index & 0x01) != 0)
overhead += 2; // Left child length, one less than half of nodes will have one.
return overhead;
}
public:
// Methods for decoding specific Node members on-demand
inline int getPrefixLen() const {
return Parser(this).prefixLen;
}
inline StringRef getSplitString() const {
return Parser(this).splitString();
}
inline StringRef getSuffixString() const {
return Parser(this).suffixString();
}
inline StringRef getValueString() const {
return Parser(this).valueString();
}
inline const Node * getLeftChild() const {
return Parser(this).leftChild();
}
inline const Node * getRightChild() const {
return Parser(this).rightChild();
}
inline int getKeySize() const {
return Parser(this).keyLen();
}
};
#pragma pack(push,1)
uint16_t size; // size in bytes
Node root;
#pragma pack(pop)
static inline int GetHeaderSize() {
return sizeof(PrefixTree) - sizeof(root);
}
private:
struct PathEntry {
const Node *node;
Node::Parser parser;
// Key may or may not point to the space within keyBuffer.
// Key will always contain at least the prefix bytes borrowed by node
// KeyBuffer will always be large enough to hold the entire reconstituted key for node
//
// These are mutable because getting key bytes from this PathEntry can change these
// but they're really just a read cache for reconstituted key bytes.
mutable StringRef key;
mutable Standalone<VectorRef<uint8_t>> keyBuffer;
// Path entry was reached by going left from the previous node
bool nodeIsLeftChild;
// number of consecutive moves in same direction
int moves;
PathEntry() : node(nullptr) {
}
PathEntry(const PathEntry &rhs) {
*this = rhs;
}
// Initialize the key byte buffer to hold bytes of a new node. Use a new arena
// if the old arena is being held by any users.
void initKeyBufferSpace() {
if(node != nullptr) {
int size = parser.keyLen();
if(keyBuffer.arena().impl && !keyBuffer.arena().impl->isSoleOwnerUnsafe()) {
keyBuffer = Standalone<VectorRef<uint8_t>>();
}
keyBuffer.reserve(keyBuffer.arena(), size);
}
}
PathEntry & operator= (const PathEntry &rhs) {
node = rhs.node;
parser = rhs.parser;
nodeIsLeftChild = rhs.nodeIsLeftChild;
moves = rhs.moves;
// New key buffer must be able to hold full reconstituted key, not just the
// part of it referenced by rhs.key (which may not be the whole thing)
initKeyBufferSpace();
if(node != nullptr && rhs.key.size() > 0) {
// Copy rhs.key into keyBuffer and set key to the destination bytes
memcpy(keyBuffer.begin(), rhs.key.begin(), rhs.key.size());
key = StringRef(keyBuffer.begin(), rhs.key.size());
}
else {
key = rhs.key;
}
return *this;
}
void init(StringRef s) {
node = nullptr;
key = s;
}
void init(const Node *_node, const PathEntry *prefixSource, bool isLeft, int numMoves) {
node = _node;
parser.init(node);
nodeIsLeftChild = isLeft;
moves = numMoves;
// keyBuffer will be large enough to hold the full reconstituted key but initially
// key will be a reference returned from prefixSource->getKeyRef()
// See comments near keyBuffer and key for more info.
initKeyBufferSpace();
key = prefixSource->getKeyRef(parser.prefixLen);
}
inline bool valid() const {
return node != nullptr;
}
int compareToKey(StringRef s) const {
// Key has at least this node's borrowed prefix bytes in it.
// If s is shorter than key, we only need to compare it to key
if(s.size() < key.size())
return s.compare(key);
int cmp = s.substr(0, key.size()).compare(key);
if(cmp != 0)
return cmp;
// The borrowed prefix bytes and possibly more have already been compared and were equal
int comparedLen = key.size();
s = s.substr(comparedLen);
StringRef split = parser.splitString();
int splitSizeOriginal = split.size();
int splitStart = comparedLen - parser.prefixLen;
if(splitStart < split.size()) {
split = split.substr(splitStart);
if(s.size() < split.size())
return s.compare(split);
cmp = s.substr(0, split.size()).compare(split);
if(cmp != 0)
return cmp;
s = s.substr(split.size());
comparedLen += split.size();
}
int suffixStart = comparedLen - (parser.prefixLen + splitSizeOriginal);
StringRef suffix = parser.suffixString();
ASSERT(suffixStart >= 0 && suffixStart <= suffix.size());
return s.compare(suffix.substr(suffixStart));
}
// Make sure that key refers to bytes in keyBuffer, copying if necessary
void ensureKeyInBuffer() const {
if(key.begin() != keyBuffer.begin()) {
memcpy(keyBuffer.begin(), key.begin(), key.size());
key = StringRef(keyBuffer.begin(), key.size());
}
}
// Get the borrowed prefix string. Key must contain all of those bytes but it could contain more.
StringRef getPrefix() const {
if(node == nullptr)
return key;
return key.substr(0, parser.prefixLen);
}
// Return a reference to the first size bytes of the key.
//
// If size <= key's size then a substring of key will be returned, but if alwaysUseKeyBuffer
// is true then before returning the existing value of key (not just the first size bytes)
// will be copied into keyBuffer and key will be updated to point there.
//
// If size is greater than key's size, then key will be moved into keyBuffer if it is not already there
// and the remaining needed bytes will be copied into keyBuffer from the split and suffix strings.
KeyRef getKeyRef(int size = -1, bool alwaysUseKeyBuffer = false) const {
if(size < 0)
size = parser.keyLen();
// If size is less than key then return a substring of it, possibly after moving it to the keyBuffer.
if(size <= key.size()) {
if(alwaysUseKeyBuffer)
ensureKeyInBuffer();
return key.substr(0, size);
}
ASSERT(node != nullptr);
ensureKeyInBuffer();
// The borrowed prefix bytes and possibly more must already be in key
int writtenLen = key.size();
StringRef split = parser.splitString();
StringRef suffix = parser.suffixString();
int splitStart = writtenLen - parser.prefixLen;
if(splitStart < split.size()) {
int splitLen = std::min(split.size() - splitStart, size - writtenLen);
memcpy(mutateString(key) + writtenLen, split.begin() + splitStart, splitLen);
writtenLen += splitLen;
}
int suffixStart = writtenLen - parser.prefixLen - split.size();
if(suffixStart < suffix.size()) {
int suffixLen = std::min(suffix.size() - suffixStart, size - writtenLen);
memcpy(mutateString(key) + writtenLen, suffix.begin() + suffixStart, suffixLen);
writtenLen += suffixLen;
}
ASSERT(writtenLen == size);
key = StringRef(key.begin(), size);
return key;
}
// Return keyRef(size) and the arena that keyBuffer resides in.
Key getKey(int size = -1) const {
StringRef k = getKeyRef(size, true);
return Key(k, keyBuffer.arena());
}
};
public:
// Cursor provides a way to seek into a PrefixTree and iterate over its content
// Seek and move methods can return false can return false if they fail to achieve the desired effect
// but a cursor will remain 'valid' as long as the tree is not empty.
//
// It coalesces prefix bytes into a contiguous buffer for each node along the traversal
// path to make iteration faster.
struct Cursor {
Cursor() : pathLen(0) {
}
Cursor(const Node *root, StringRef prevAncestor, StringRef nextAncestor) {
init(root, prevAncestor, nextAncestor);
}
static const int initialPathLen = 3;
static const int initialPathCapacity = 20;
// This is a separate function so that Cursors can be reused to search different PrefixTrees
// which avoids cursor destruction and creation which involves unnecessary memory churn.
// The root node is arbitrarily assumed to be a right child of prevAncestor which itself is a left child of nextAncestor
void init(const Node *root, StringRef prevAncestor, StringRef nextAncestor) {
if(path.size() < initialPathCapacity)
path.resize(initialPathCapacity);
pathLen = initialPathLen;
path[0].init(nextAncestor);
path[1].init(prevAncestor);
path[2].init(root, &path[root->flags & Node::PREFIX_SOURCE_NEXT ? 0 : 1], false, 1);
}
bool operator == (const Cursor &rhs) const {
return pathBack().node == rhs.pathBack().node;
}
StringRef leftParentBoundary;
StringRef rightParentBoundary;
std::vector<PathEntry> path;
// pathLen is the number of elements in path which are in use. This is to prevent constantly destroying
// and constructing PathEntry objects which would unnecessarily churn through memory in Arena for storing
// coalesced prefixes.
int pathLen;
bool valid() const {
return pathLen != 0 && pathBack().valid();
}
// Get a reference to the current key which is valid until the Cursor is moved.
KeyRef getKeyRef() const {
return pathBack().getKeyRef();
}
// Get a Standalone<KeyRef> for the current key which will still be valid after the Cursor is moved.
Key getKey() const {
return pathBack().getKey();
}
// Get a reference to the current value which is valid as long as the Cursor's page memory exists.
ValueRef getValueRef() const {
return pathBack().parser.valueString();
}
// Get a key/value reference that is valid until the Cursor is moved.
EntryRef getKVRef() const {
return EntryRef(getKeyRef(), getValueRef());
}
// Returns a standalone EntryRef where both key and value exist in the standalone's arena,
// unless copyValue is false in which case the value will be a reference into tree memory.
Entry getKV(bool copyValue = true) const {
Key k = getKey();
ValueRef v = getValueRef();
if(copyValue)
v = ValueRef(k.arena(), getValueRef());
return Entry(EntryRef(k, v), k.arena());
}
// 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(StringRef s) {
if(pathLen == 0)
return false;
pathLen = initialPathLen;
// TODO: Track position of difference and use prefix reuse bytes and prefix sources
// to skip comparison of some prefix bytes when possible
while(1) {
const PathEntry &p = pathBack();
const Node *right = p.parser.rightChild();
_mm_prefetch((const char*)right, _MM_HINT_T0);
int cmp = p.compareToKey(s);
if(cmp == 0)
return true;
if(cmp < 0) {
// Try to traverse left
const Node *left = p.parser.leftChild();
if(left == nullptr) {
// If we're at the root, cursor should now be before the first element
if(pathLen == initialPathLen) {
return false;
}
if(p.nodeIsLeftChild) {
// If we only went left, cursor should now be before the first element
if((p.moves + initialPathLen) == pathLen) {
return false;
}
// Otherwise, go to the parent of the last right child traversed,
// which is the last node from which we went right
popPath(p.moves + 1);
return true;
}
// p.directionLeft is false, so p.node is a right child, so go to its parent.
popPath(1);
return true;
}
int newMoves = p.nodeIsLeftChild ? p.moves + 1 : 1;
const PathEntry *borrowSource = (left->flags & Node::PREFIX_SOURCE_NEXT) ? &p : &p - newMoves;
pushPath(left, borrowSource, true, newMoves);
}
else {
// Try to traverse right
if(right == nullptr) {
return true;
}
int newMoves = p.nodeIsLeftChild ? 1 : p.moves + 1;
const PathEntry *borrowSource = (right->flags & Node::PREFIX_SOURCE_NEXT) ? &p - newMoves : &p;
pushPath(right, borrowSource, false, newMoves);
}
}
}
inline const PathEntry &pathBack() const {
return path[pathLen - 1];
}
inline PathEntry &pathBack() {
return path[pathLen - 1];
}
inline void pushPath(const Node *node, const PathEntry *borrowSource, bool left, int moves) {
++pathLen;
if(path.size() < pathLen) {
path.resize(pathLen);
}
pathBack().init(node, borrowSource, left, moves);
}
inline void popPath(int n) {
pathLen -= n;
}
std::string pathToString() const {
std::string s;
for(int i = 0; i < pathLen; ++i) {
s += format("(%d: ", i);
const Node *node = path[i].node;
if(node != nullptr) {
s += "childDir=";
s += (path[i].nodeIsLeftChild ? "left " : "right ");
}
s += format("prefix='%s'", path[i].getPrefix().toHexString(20).c_str());
if(node != nullptr) {
s += format(" split='%s' suffix='%s' value='%s'", node->getSplitString().toHexString(20).c_str(), node->getSuffixString().toHexString(20).c_str(), node->getValueString().toHexString(20).c_str());
}
else
s += ") ";
}
return s;
}
bool moveFirst() {
if(pathLen == 0)
return false;
pathLen = initialPathLen;
while(1) {
const PathEntry &p = pathBack();
const Node *left = p.parser.leftChild();
if(left == nullptr)
break;
// TODO: This can be simpler since it only goes left
int newMoves = p.nodeIsLeftChild ? p.moves + 1 : 1;
const PathEntry *borrowSource = (left->flags & Node::PREFIX_SOURCE_NEXT) ? &p : &p - newMoves;
pushPath(left, borrowSource, true, newMoves);
}
return true;
}
bool moveLast() {
if(pathLen == 0)
return false;
pathLen = initialPathLen;
while(1) {
const PathEntry &p = pathBack();
const Node *right = p.parser.rightChild();
if(right == nullptr)
break;
// TODO: This can be simpler since it only goes right
int newMoves = p.nodeIsLeftChild ? 1 : p.moves + 1;
const PathEntry *borrowSource = (right->flags & Node::PREFIX_SOURCE_NEXT) ? &p - newMoves : &p;
pushPath(right, borrowSource, false, newMoves);
}
return true;
}
bool moveNext() {
const PathEntry &p = pathBack();
// If p isn't valid
if(!p.valid()) {
return false;
}
const Node *right = p.parser.rightChild();
// If we can't go right, then go upward to the parent of the last left child
if(right == nullptr) {
// If current node was a left child then pop one node and we're done
if(p.nodeIsLeftChild) {
popPath(1);
return true;
}
// Current node is a right child.
// If we are at the rightmost tree node return false and don't move.
if(p.moves + initialPathLen - 1 == pathLen) {
return false;
}
// Truncate path to the parent of the last left child
popPath(p.moves + 1);
return true;
}
// Go right
int newMoves = p.nodeIsLeftChild ? 1 : p.moves + 1;
const PathEntry *borrowSource = (right->flags & Node::PREFIX_SOURCE_NEXT) ? &p - newMoves : &p;
pushPath(right, borrowSource, false, newMoves);
// Go left as far as possible
while(1) {
const PathEntry &p = pathBack();
const Node *left = p.parser.leftChild();
if(left == nullptr) {
return true;
}
int newMoves = p.nodeIsLeftChild ? p.moves + 1 : 1;
const PathEntry *borrowSource = (left->flags & Node::PREFIX_SOURCE_NEXT) ? &p : &p - newMoves;
pushPath(left, borrowSource, true, newMoves);
}
}
bool movePrev() {
const PathEntry &p = pathBack();
// If p isn't valid
if(!p.valid()) {
return false;
}
const Node *left = p.parser.leftChild();
// If we can't go left, then go upward to the parent of the last right child
if(left == nullptr) {
// If current node was a right child
if(!p.nodeIsLeftChild) {
// If we are at the root then don't move and return false.
if(pathLen == initialPathLen)
return false;
// Otherwise, pop one node from the path and return true.
popPath(1);
return true;
}
// Current node is a left child.
// If we are at the leftmost tree node then return false and don't move.
if(p.moves + 3 == pathLen) {
return false;
}
// Truncate path to the parent of the last right child
popPath(p.moves + 1);
return true;
}
// Go left
int newMoves = p.nodeIsLeftChild ? p.moves + 1 : 1;
const PathEntry *borrowSource = (left->flags & Node::PREFIX_SOURCE_NEXT) ? &p : &p - newMoves;
pushPath(left, borrowSource, true, newMoves);
// Go right as far as possible
while(1) {
const PathEntry &p = pathBack();
const Node *right = p.parser.rightChild();
if(right == nullptr) {
return true;
}
int newMoves = p.nodeIsLeftChild ? 1 : p.moves + 1;
const PathEntry *borrowSource = (right->flags & Node::PREFIX_SOURCE_NEXT) ? &p - newMoves : &p;
pushPath(right, borrowSource, false, newMoves);
}
}
};
Cursor getCursor(StringRef prevAncestor, StringRef nextAncestor) const {
return (size != 0) ? Cursor(&root, prevAncestor, nextAncestor) : Cursor();
}
static std::string escapeForDOT(StringRef s) {
std::string r = "\"";
for(char c : s) {
if(c == '\n')
r += "\\n";
else if(isprint(c) && c != '"')
r += c;
else
r += format("{%02X}", c);
}
return r + '"';
}
std::string toDOT(StringRef prevAncestor, StringRef nextAncestor) const {
auto c = getCursor(prevAncestor, nextAncestor);
c.moveFirst();
std::string r;
r += format("digraph PrefixTree%p {\n", this);
do {
const PathEntry &p = c.pathBack();
const Node *n = p.node;
const Node *left = p.parser.leftChild();
const Node *right = p.parser.rightChild();
std::string label = escapeForDOT(format("PrefixSource: %s\nPrefix: [%s]\nSplit: %s\nSuffix: %s",
n->flags & Node::PREFIX_SOURCE_NEXT ? "Left" : "Right",
p.getPrefix().toString().c_str(),
p.parser.splitString().toString().c_str(),
p.parser.suffixString().toString().c_str()
));
r += format("node%p [ label = %s ];\nnode%p -> { %s %s };\n", n, label.c_str(), n,
left ? format("node%p", left).c_str() : "",
right ? format("node%p", right).c_str() : ""
);
} while(c.moveNext());
r += "}\n";
return r;
}
// Returns number of bytes written
int build(const EntryRef *begin, const EntryRef *end, StringRef prevAncestor, StringRef nextAncestor) {
// The boundary leading to the new page acts as the last time we branched right
if(begin == end) {
size = 0;
}
else {
size = sizeof(size) + build(root, begin, end, nextAncestor, prevAncestor);
}
ASSERT(size <= MaximumTreeSize());
return size;
}
private:
static uint16_t build(Node &root, const EntryRef *begin, const EntryRef *end, const StringRef &nextAncestor, const StringRef &prevAncestor) {
ASSERT(end != begin);
int count = end - begin;
// Find key to be stored in root
int mid = perfectSubtreeSplitPointCached(count);
const StringRef &key = begin[mid].key;
const StringRef &val = begin[mid].value;
// Since key must be between lastLeft and lastRight, any common prefix they share must be shared by key
// so rather than comparing all of key to each one separately we can just compare lastLeft and lastRight
// to each other and then skip over the resulting length in key
int nextPrevCommon = commonPrefixLength(nextAncestor.begin(), prevAncestor.begin(), std::min(nextAncestor.size(), prevAncestor.size()));
// Pointer to remainder of key after the left/right common bytes
const uint8_t *keyExt = key.begin() + nextPrevCommon;
// Find out how many bytes beyond leftRightCommon key has with each last left/right string separately
int extNext = commonPrefixLength(keyExt, nextAncestor.begin() + nextPrevCommon, std::min(key.size(), nextAncestor.size()) - nextPrevCommon);
int extPrev = commonPrefixLength(keyExt, prevAncestor.begin() + nextPrevCommon, std::min(key.size(), prevAncestor.size()) - nextPrevCommon);
// Use the longer result
bool prefixSourceNext = extNext > extPrev;
int prefixLen = nextPrevCommon + (prefixSourceNext ? extNext : extPrev);
int splitLen; // Bytes after prefix required to make traversal decision
int suffixLen; // Remainder of key bytes after split key portion
//printf("build: '%s'\n prefixLen %d prefixSourceNext %d\n", key.toHexString(20).c_str(), prefixLen, prefixSourceNext);
// 2 entries or less means no right child, so just put all remaining key bytes into split string.
if(count < 3) {
splitLen = key.size() - prefixLen;
suffixLen = 0;
}
else {
// There are 2 children
// Avoid using the suffix at all if the remainder is small enough.
splitLen = key.size() - prefixLen;
if(splitLen < SERVER_KNOBS->PREFIX_TREE_IMMEDIATE_KEY_SIZE_LIMIT) {
suffixLen = 0;
}
else {
// Remainder of the key was not small enough to put entirely before the left child, so find the actual required to make the branch decision
const StringRef &prevKey = begin[mid - 1].key;
splitLen = commonPrefixLength(key.begin(), prevKey.begin(), std::min(key.size(), prevKey.size())) + 1 - prefixLen;
// Put at least the minimum immediate byte count in the split key (before the left child)
if(splitLen < SERVER_KNOBS->PREFIX_TREE_IMMEDIATE_KEY_SIZE_MIN)
splitLen = std::min(key.size() - prefixLen, SERVER_KNOBS->PREFIX_TREE_IMMEDIATE_KEY_SIZE_MIN);
suffixLen = key.size() - splitLen - prefixLen;
}
}
// We now know enough about the fields present and their lengths to set the flag bits and write a header
// If any int is more than 8 bits then use large ints
bool large = prefixLen > 255 || splitLen > 255 || suffixLen > 255 || val.size() > 255;
root.flags = large ? Node::USE_LARGE_LENGTHS : 0;
if(prefixSourceNext)
root.flags |= Node::PREFIX_SOURCE_NEXT;
union {
uint8_t *p8;
uint16_t *p16;
};
p8 = &root.flags + 1;
if(large)
*p16++ = prefixLen;
else
*p8++ = prefixLen;
if(splitLen > 0) {
root.flags |= Node::HAS_SPLIT;
if(large)
*p16++ = splitLen;
else
*p8++ = splitLen;
}
uint16_t *pLeftLen = p16;
if(count > 1) {
++p16;
}
if(suffixLen > 0) {
root.flags |= Node::HAS_SUFFIX;
if(large)
*p16++ = suffixLen;
else
*p8++ = suffixLen;
}
if(val.size() > 0) {
root.flags |= Node::HAS_VALUE;
if(large)
*p16++ = val.size();
else
*p8++ = val.size();
}
// Header is written, now write strings and children in order.
const uint8_t *keyPtr = key.begin() + prefixLen;
// Serialize split bytes
if(splitLen > 0) {
memcpy(p8, keyPtr, splitLen);
p8 += splitLen;
keyPtr += splitLen;
}
// Serialize left child
if(count > 1) {
root.flags |= Node::HAS_LEFT_CHILD;
int leftLen = build(*(Node *)(p8), begin, begin + mid, key, prevAncestor);
*pLeftLen = leftLen;
p8 += leftLen;
}
// Serialize suffix bytes
if(suffixLen > 0) {
memcpy(p8, keyPtr, suffixLen);
p8 += suffixLen;
}
// Serialize value bytes
if(val.size() > 0) {
memcpy(p8, val.begin(), val.size());
p8 += val.size();
}
// Serialize right child
if(count > 2) {
root.flags |= Node::HAS_RIGHT_CHILD;
int rightLen = build(*(Node *)(p8), begin + mid + 1, end, nextAncestor, key);
p8 += rightLen;
}
/*
printf("\nBuilt: key '%s' c %d p %d spl %d suf %d\nRaw: %s\n", key.toString().c_str(), count, prefixLen, splitLen, suffixLen, StringRef(&root.flags, p8 - &root.flags).toHexString(20).c_str());
Node::Parser p(&root);
printf("parser: headerLen %d prefixLen %d leftPos %d rightPos %d split %s suffix %s val %s\n",
p.headerLen, p.prefixLen, p.leftPos, p.rightPos, p.splitString().toString().c_str(), p.suffixString().toString().c_str(), p.valueString().toString().c_str());
*/
return p8 - (uint8_t *)&root;
}
};