convert interval tree from using a randomized bst to a red-black tree

This commit is contained in:
Anthony Pesch 2015-09-21 17:05:55 -07:00
parent 0d455e710a
commit 07a1156eb5
5 changed files with 556 additions and 237 deletions

View File

@ -3,102 +3,87 @@
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <assert.h>
#include <stdlib.h>
#include "core/assert.h"
#include "core/intrusive_tree.h"
namespace dreavm {
// Interval tree implemented using a randomized bst. Based on implementation at
// http://algs4.cs.princeton.edu/93intersection/IntervalST.java
//
// Parent pointers have been added in order to make removal by node (as opposed
// to key) possible.
typedef int64_t interval_type;
template <typename IT, typename VT>
class IntervalTree {
template <typename T>
struct IntervalNode : public IntrusiveTreeNode<IntervalNode<T>> {
IntervalNode(const interval_type &low, const interval_type &high,
const T &value)
: low(low), high(high), max(high), value(value), num(1), height(1) {}
bool operator<(const IntervalNode<T> &rhs) const {
return low < rhs.low || (low == rhs.low && high < rhs.high);
}
interval_type low, high, max;
T value;
int num, height;
};
template <typename T>
class IntervalTree : public IntrusiveTree<IntervalNode<T>> {
public:
struct Node;
typedef IT interval_type;
typedef VT value_type;
typedef IntervalTree<interval_type, value_type> self_type;
typedef std::function<void(const self_type &, Node *)> iterate_cb;
struct Node {
Node(const interval_type &low, const interval_type &high,
const value_type &value)
: parent(nullptr),
left(nullptr),
right(nullptr),
low(low),
high(high),
max(high),
value(value),
num(1) {}
bool operator<(const Node &rhs) const {
return low < rhs.low || (low == rhs.low && high < rhs.high);
}
Node *parent, *left, *right;
interval_type low, high, max;
value_type value;
int num;
};
IntervalTree() : root_(nullptr) {}
typedef IntervalTree<T> self_type;
typedef IntervalNode<T> node_type;
typedef std::function<void(const self_type &, node_type *)> iterate_cb;
~IntervalTree() { Clear(); }
int Size() { return Size(root_); }
int Size() { return Size(this->root_); }
int Height() { return Height(root_); }
int Height() { return Height(this->root_); }
Node *Insert(const interval_type &low, const interval_type &high,
const value_type &value) {
Node *n = new Node(low, high, value);
node_type *Insert(const interval_type &low, const interval_type &high,
const T &value) {
node_type *n = new node_type(low, high, value);
SetRoot(RandomizedInsert(root_, n));
// add new node into the correct location in the tree, then link it in
// to recolor the tree
node_type *parent = this->root_;
while (parent) {
if (*n < *parent) {
if (!parent->left) {
parent->left = n;
break;
}
parent = parent->left;
} else {
if (!parent->right) {
parent->right = n;
break;
}
parent = parent->right;
}
}
n->parent = parent;
this->Link(n);
return n;
}
void Remove(Node *n) {
// join left and right subtrees, assign new joined subtree to parent
Node *joined = Join(n->left, n->right);
void Remove(node_type *n) {
this->Unlink(n);
if (!n->parent) {
// removed node had no parent, must have been root
CHECK_EQ(root_, n);
SetRoot(joined);
} else if (n->parent->left == n) {
SetLeft(n->parent, joined);
} else {
SetRight(n->parent, joined);
}
// fix up each node in the parent chain
Node *parent = n->parent;
while (parent) {
FixCounts(parent);
parent = parent->parent;
}
// remove the node
delete n;
}
void Clear() {
Clear(root_);
Clear(this->root_);
SetRoot(nullptr);
this->root_ = nullptr;
}
Node *Find(interval_type low, interval_type high) {
Node *n = root_;
node_type *Find(interval_type low, interval_type high) {
node_type *n = this->root_;
while (n) {
if (high >= n->low && n->high >= low) {
@ -114,58 +99,39 @@ class IntervalTree {
}
void Iterate(interval_type low, interval_type high, iterate_cb cb) {
Iterate(root_, low, high, cb);
Iterate(this->root_, low, high, cb);
}
protected:
void AugmentPropagate(node_type *n) {
while (n) {
FixCounts(n);
n = n->parent;
}
}
void AugmentRotate(node_type *oldn, node_type *newn) {
FixCounts(oldn);
FixCounts(newn);
FixCounts(newn->parent);
}
private:
int Size(Node *n) { return n ? n->num : 0; }
int Size(node_type *n) { return n ? n->num : 0; }
int Height(node_type *n) { return n ? n->height : 0; }
interval_type Max(node_type *n) { return n ? n->max : 0; }
int Height(Node *n) {
return n ? 1 + std::max(Height(n->left), Height(n->right)) : 0;
void FixCounts(node_type *n) {
if (!n) {
return;
}
n->num = 1 + Size(n->left) + Size(n->right);
n->height = 1 + std::max(Height(n->left), Height(n->right));
n->max = std::max(std::max(n->high, Max(n->left)), Max(n->right));
}
//
// insertion
//
Node *RootInsert(Node *root, Node *n) {
if (!root) {
return n;
}
if (*n < *root) {
SetLeft(root, RootInsert(root->left, n));
root = RotateRight(root);
} else {
SetRight(root, RootInsert(root->right, n));
root = RotateLeft(root);
}
return root;
}
Node *RandomizedInsert(Node *root, Node *n) {
if (!root) {
return n;
}
// make new node the root with uniform probability
if (rand() % (Size(root) + 1) == 0) {
return RootInsert(root, n);
}
if (*n < *root) {
SetLeft(root, RandomizedInsert(root->left, n));
} else {
SetRight(root, RandomizedInsert(root->right, n));
}
return root;
}
//
// removal
//
void Clear(Node *n) {
void Clear(node_type *n) {
if (!n) {
return;
}
@ -176,10 +142,8 @@ class IntervalTree {
delete n;
}
//
// iteration
//
bool Iterate(Node *n, interval_type low, interval_type high, iterate_cb cb) {
bool Iterate(node_type *n, interval_type low, interval_type high,
iterate_cb cb) {
if (!n) {
return false;
}
@ -203,106 +167,6 @@ class IntervalTree {
return found1 || found2 || found3;
}
//
// helper methods
//
void SetRoot(Node *n) {
root_ = n;
if (root_) {
root_->parent = nullptr;
}
}
void SetLeft(Node *parent, Node *n) {
parent->left = n;
if (parent->left) {
parent->left->parent = parent;
}
FixCounts(parent);
}
void SetRight(Node *parent, Node *n) {
parent->right = n;
if (parent->right) {
parent->right->parent = parent;
}
FixCounts(parent);
}
void FixCounts(Node *n) {
if (!n) {
return;
}
n->num = 1 + Size(n->left) + Size(n->right);
n->max = n->high;
if (n->left) {
n->max = std::max(n->max, n->left->max);
}
if (n->right) {
n->max = std::max(n->max, n->right->max);
}
}
Node *RotateRight(Node *root) {
Node *parent = root->parent;
Node *n = root->left;
SetLeft(root, n->right);
SetRight(n, root);
if (parent) {
if (parent->left == root) {
SetLeft(parent, n);
} else {
SetRight(parent, n);
}
}
return n;
}
Node *RotateLeft(Node *root) {
Node *parent = root->parent;
Node *n = root->right;
SetRight(root, n->left);
SetLeft(n, root);
if (parent) {
if (parent->left == root) {
SetLeft(parent, n);
} else {
SetRight(parent, n);
}
}
return n;
}
Node *Join(Node *a, Node *b) {
if (!a) {
return b;
} else if (!b) {
return a;
}
if ((rand() % (Size(a) + Size(b))) < Size(a)) {
SetRight(a, Join(a->right, b));
return a;
} else {
SetLeft(b, Join(a, b->left));
return b;
}
}
Node *root_;
};
}

452
src/core/intrusive_tree.h Normal file
View File

@ -0,0 +1,452 @@
#ifndef INTRUSIVE_TREE_H
#define INTRUSIVE_TREE_H
#include <functional>
#include <stdlib.h>
#include "core/assert.h"
namespace dreavm {
enum Color { RED = true, BLACK = false };
template <typename T>
struct IntrusiveTreeNode {
IntrusiveTreeNode()
: parent(nullptr), left(nullptr), right(nullptr), color(RED) {}
T *parent, *left, *right;
Color color;
};
template <typename T>
class IntrusiveTree {
protected:
IntrusiveTree() : root_(nullptr) {}
virtual ~IntrusiveTree() {}
virtual void AugmentPropagate(T *n) = 0;
virtual void AugmentRotate(T *oldn, T *newn) = 0;
T *Link(T *n) {
// set initial root
if (!root_) {
root_ = n;
}
// adjust tree, starting at the newly inserted node, to satisfy the
// properties of a valid red-black tree
LinkCase1(n);
// force root to black
root_->color = BLACK;
AugmentPropagate(n->parent);
#ifdef VERIFY_INTRUSIVE_TREE
VerifyProperties();
#endif
return n;
}
void Unlink(T *n) {
// when deleting a node with two non-leaf children, we swap the node with
// its in-order predecessor (the maximum or rightmost element in the left
// subtree), and then delete the original node which now has only one
// non-leaf child
if (n->left && n->right) {
T *pred = MaximumNode(n->left);
SwapNode(n, pred);
}
// a node with at most one non-leaf child can simply be replaced with its
// non-leaf child
CHECK(!n->left || !n->right);
T *child = n->right ? n->right : n->left;
if (color(n) == BLACK) {
UnlinkCase1(n);
}
ReplaceNode(n, child);
// force root to black
if (root_) {
root_->color = BLACK;
}
// fix up each node in the parent chain
AugmentPropagate(n->parent);
#ifdef VERIFY_INTRUSIVE_TREE
VerifyProperties();
#endif
}
T *root_;
private:
static inline T *grandparent(T *n) {
CHECK_NOTNULL(n);
CHECK_NOTNULL(n->parent); // not the root node
CHECK_NOTNULL(n->parent->parent); // not child of root
return n->parent->parent;
}
static inline T *sibling(T *n) {
CHECK_NOTNULL(n);
CHECK_NOTNULL(n->parent); // root node has no sibling
if (n == n->parent->left) {
return n->parent->right;
} else {
return n->parent->left;
}
}
static inline T *uncle(T *n) {
CHECK_NOTNULL(n);
CHECK_NOTNULL(n->parent); // root node has no uncle
CHECK_NOTNULL(n->parent->parent); // children of root have no uncle
return sibling(n->parent);
}
static inline Color color(T *n) { return n ? n->color : BLACK; }
static void VerifyProperty1(T *root) { CHECK_EQ(color(root), BLACK); }
// Every red node has two children, and both are black (or equivalently, the
// parent of every red node is black).
static void VerifyProperty2(T *n) {
if (!n) {
return;
}
if (color(n) == RED) {
CHECK_EQ(color(n->left), BLACK);
CHECK_EQ(color(n->right), BLACK);
CHECK_EQ(color(n->parent), BLACK);
}
VerifyProperty2(n->left);
VerifyProperty2(n->right);
}
// All paths from any given node to its leaf nodes contain the same number of
// black nodes. This one is the trickiest to verify; we do it by traversing
// the tree, incrementing a black node count as we go. The first time we reach
// a leaf we save the count. We return the count so that when we subsequently
// reach other leaves, we compare the count to the saved count.
static int VerifyProperty3(T *n, int black_count = 0,
int path_black_count = -1) {
if (color(n) == BLACK) {
black_count++;
}
if (!n) {
if (path_black_count == -1) {
path_black_count = black_count;
} else {
CHECK_EQ(black_count, path_black_count);
}
return path_black_count;
}
path_black_count = VerifyProperty3(n->left, black_count, path_black_count);
path_black_count = VerifyProperty3(n->right, black_count, path_black_count);
return path_black_count;
}
void VerifyProperties() {
VerifyProperty1(root_);
VerifyProperty2(root_);
VerifyProperty3(root_);
}
// n r
// r -> n
// l l
void RotateLeft(T *n) {
T *r = n->right;
ReplaceNode(n, r);
n->right = r->left;
if (n->right) {
n->right->parent = n;
}
r->left = n;
r->left->parent = r;
AugmentRotate(n, r);
}
// n l
// l -> n
// r r
void RotateRight(T *n) {
T *l = n->left;
T *r = l->right;
ReplaceNode(n, l);
n->left = r;
if (n->left) {
n->left->parent = n;
}
l->right = n;
l->right->parent = l;
AugmentRotate(n, l);
}
void ReplaceNode(T *oldn, T *newn) {
if (oldn->parent) {
if (oldn == oldn->parent->left) {
oldn->parent->left = newn;
} else {
oldn->parent->right = newn;
}
} else {
root_ = newn;
}
if (newn) {
newn->parent = oldn->parent;
}
}
void SwapNode(T *a, T *b) {
T tmp = *a;
// note, swapping pointers is complicated by the case where a parent is
// being swapped with its child, for example:
// a -> b
// b a
// in this case, a swap(a, b) would result in a->parent == a, when it
// should be b
if ((a->parent = b->parent == a ? b : b->parent)) {
if (a->parent->left == b) {
a->parent->left = a;
} else if (a->parent->right == b) {
a->parent->right = a;
}
} else {
root_ = a;
}
if ((a->left = b->left == a ? b : b->left)) {
a->left->parent = a;
}
if ((a->right = b->right == a ? b : b->right)) {
a->right->parent = a;
}
a->color = b->color;
if ((b->parent = tmp.parent == b ? a : tmp.parent)) {
if (b->parent->left == a) {
b->parent->left = b;
} else if (b->parent->right == a) {
b->parent->right = b;
}
} else {
root_ = b;
}
if ((b->left = tmp.left == b ? a : tmp.left)) {
b->left->parent = b;
}
if ((b->right = tmp.right == b ? a : tmp.right)) {
b->right->parent = b;
}
b->color = tmp.color;
}
T *MaximumNode(T *n) {
while (n->right) {
n = n->right;
}
return n;
}
// In this case, the new node is now the root node of the tree. Since the root
// node must be black, and changing its color adds the same number of black
// nodes to every path, we simply recolor it black. Because only the root node
// has no parent, we can assume henceforth that the node has a parent.
void LinkCase1(T *n) {
if (!n->parent) {
return;
}
LinkCase2(n);
}
// In this case, the new node has a black parent. All the properties are still
// satisfied and we return->
void LinkCase2(T *n) {
if (color(n->parent) == BLACK) {
return; // Tree is still valid
}
LinkCase3(n);
}
// In this case, the uncle node is red. We recolor the parent and uncle black
// and the grandparent red. However, the red grandparent node may now violate
// the red-black tree properties; we recursively invoke this procedure on it
// from case 1 to deal with this.
void LinkCase3(T *n) {
if (color(uncle(n)) == RED) {
n->parent->color = BLACK;
uncle(n)->color = BLACK;
grandparent(n)->color = RED;
LinkCase1(grandparent(n));
return;
}
LinkCase4(n);
}
// In this case, we deal with two cases that are mirror images of one another:
// * The new node is the right child of its parent and the parent is the left
// child of the grandparent. In this case we rotate left about the parent.
// * The new node is the left child of its parent and the parent is the right
// child of the grandparent. In this case we rotate right about the parent.
// Neither of these fixes the properties, but they put the tree in the correct
// form to apply case 5.
void LinkCase4(T *n) {
if (n == n->parent->right && n->parent == grandparent(n)->left) {
RotateLeft(n->parent);
n = n->left;
} else if (n == n->parent->left && n->parent == grandparent(n)->right) {
RotateRight(n->parent);
n = n->right;
}
LinkCase5(n);
}
// In this final case, we deal with two cases that are mirror images of one
// another:
// * The new node is the left child of its parent and the parent is the left
// child of the grandparent. In this case we rotate right about the
// grandparent.
// * The new node is the right child of its parent and the parent is the right
// child of the grandparent. In this case we rotate left about the
// grandparent.
// Now the properties are satisfied and all cases have been covered.
void LinkCase5(T *n) {
n->parent->color = BLACK;
grandparent(n)->color = RED;
if (n == n->parent->left && n->parent == grandparent(n)->left) {
RotateRight(grandparent(n));
} else {
CHECK(n == n->parent->right && n->parent == grandparent(n)->right);
RotateLeft(grandparent(n));
}
}
// In this case, N has become the root node. The deletion removed one black
// node from every path, so no properties are violated.
void UnlinkCase1(T *n) {
if (!n->parent) {
return;
}
UnlinkCase2(n);
}
// N has a red sibling. In this case we exchange the colors of the parent and
// sibling, then rotate about the parent so that the sibling becomes the
// parent of its former parent. This does not restore the tree properties, but
// reduces the problem to one of the remaining cases.
void UnlinkCase2(T *n) {
if (color(sibling(n)) == RED) {
n->parent->color = RED;
sibling(n)->color = BLACK;
if (n == n->parent->left) {
RotateLeft(n->parent);
} else {
RotateRight(n->parent);
}
}
UnlinkCase3(n);
}
// In this case N's parent, sibling, and sibling's children are black. In this
// case we paint the sibling red. Now all paths passing through N's parent
// have one less black node than before the deletion, so we must recursively
// run this procedure from case 1 on N's parent.
void UnlinkCase3(T *n) {
if (color(n->parent) == BLACK && color(sibling(n)) == BLACK &&
color(sibling(n)->left) == BLACK && color(sibling(n)->right) == BLACK) {
sibling(n)->color = RED;
UnlinkCase1(n->parent);
return;
}
UnlinkCase4(n);
}
// N's sibling and sibling's children are black, but its parent is red. We
// exchange the colors of the sibling and parent; this restores the tree
// properties.
void UnlinkCase4(T *n) {
if (color(n->parent) == RED && color(sibling(n)) == BLACK &&
color(sibling(n)->left) == BLACK && color(sibling(n)->right) == BLACK) {
sibling(n)->color = RED;
n->parent->color = BLACK;
return;
}
UnlinkCase5(n);
}
// There are two cases handled here which are mirror images of one another:
// * N's sibling S is black, S's left child is red, S's right child is black,
// and N is the left child of its parent. We exchange the colors of S and its
// left sibling and rotate right at S.
// * N's sibling S is black, S's right child is red, S's left child is black,
// and N is the right child of its parent. We exchange the colors of S and its
// right sibling and rotate left at S.
// Both of these function to reduce us to the situation described in case 6.
void UnlinkCase5(T *n) {
if (n == n->parent->left && color(sibling(n)) == BLACK &&
color(sibling(n)->left) == RED && color(sibling(n)->right) == BLACK) {
sibling(n)->color = RED;
sibling(n)->left->color = BLACK;
RotateRight(sibling(n));
} else if (n == n->parent->right && color(sibling(n)) == BLACK &&
color(sibling(n)->right) == RED &&
color(sibling(n)->left) == BLACK) {
sibling(n)->color = RED;
sibling(n)->right->color = BLACK;
RotateLeft(sibling(n));
}
UnlinkCase6(n);
}
// There are two cases handled here which are mirror images of one another:
// * N's sibling S is black, S's right child is red, and N is the left child
// of its parent. We exchange the colors of N's parent and sibling, make S's
// right child black, then rotate left at N's parent.
// * N's sibling S is black, S's left child is red, and N is the right child
// of its parent. We exchange the colors of N's parent and sibling, make S's
// left child black, then rotate right at N's parent.
//
// This accomplishes three things at once:
// * We add a black node to all paths through N, either by adding a black S to
// those paths or by recoloring N's parent black.
// * We remove a black node from all paths through S's red child, either by
// removing P from those paths or by recoloring S.
// * We recolor S's red child black, adding a black node back to all paths
// through S's red child.
//
// S's left child has become a child of N's parent during the rotation and so
// is unaffected.
void UnlinkCase6(T *n) {
sibling(n)->color = color(n->parent);
n->parent->color = BLACK;
if (n == n->parent->left) {
CHECK_EQ(color(sibling(n)->right), RED);
sibling(n)->right->color = BLACK;
RotateLeft(n->parent);
} else {
CHECK_EQ(color(sibling(n)->left), RED);
sibling(n)->left->color = BLACK;
RotateRight(n->parent);
}
}
};
}
#endif

View File

@ -106,12 +106,14 @@ void TextureCache::Clear() {
for (auto it = textures_.begin(), e = textures_.end(); it != e; ++it) {
Invalidate(it);
}
watches_.Clear();
}
void TextureCache::CheckWrite(uint32_t addr) {
PROFILER_GPU("TextureCache::CheckWrite");
TextureWatchTree::Node *node = watches_.Find(addr, addr);
TextureWatchTree::node_type *node = watches_.Find(addr, addr);
bool handled = node != nullptr;

View File

@ -18,15 +18,15 @@ namespace holly {
struct TextureEntry;
typedef std::unordered_map<TextureKey, TextureEntry> TextureCacheMap;
typedef IntervalTree<uint32_t, TextureKey> TextureWatchTree;
typedef IntervalTree<TextureKey> TextureWatchTree;
struct TextureEntry {
TextureEntry(renderer::TextureHandle handle)
: handle(handle), texture_watch(nullptr), palette_watch(nullptr) {}
renderer::TextureHandle handle;
TextureWatchTree::Node *texture_watch;
TextureWatchTree::Node *palette_watch;
TextureWatchTree::node_type *texture_watch;
TextureWatchTree::node_type *palette_watch;
};
class TextureCache : public TextureProvider {

View File

@ -2,11 +2,13 @@
#include <set>
#include "gtest/gtest.h"
#include "core/core.h"
#define VERIFY_INTRUSIVE_TREE
#include "core/interval_tree.h"
using namespace dreavm;
typedef IntervalTree<uint32_t, void *> TestTree;
typedef IntervalTree<void *> TestTree;
typedef TestTree::node_type TestNode;
class IntervalTreeTest : public ::testing::Test {
public:
@ -18,7 +20,7 @@ class IntervalTreeTest : public ::testing::Test {
void SetUp() {
// insert dummy intervals
for (int i = 0; i < 0x10000; i++) {
for (int i = 0; i < 0x1000; i++) {
uint32_t low = 0;
uint32_t high = 0;
@ -27,13 +29,13 @@ class IntervalTreeTest : public ::testing::Test {
high = low + INTERVAL;
}
TestTree::Node *n = intervals.Insert(low, high, nullptr);
TestNode *n = intervals.Insert(low, high, nullptr);
nodes.insert(n);
}
}
TestTree intervals;
std::set<TestTree::Node *> nodes;
std::set<TestNode *> nodes;
};
TEST_F(IntervalTreeTest, ValidateRelations) {
@ -53,10 +55,9 @@ TEST_F(IntervalTreeTest, Size) {
}
TEST_F(IntervalTreeTest, Height) {
// make sure height is within expected range of ~2*log2(n)
int height = intervals.Height();
int size = intervals.Size();
ASSERT_TRUE(height >= log2(size) && height < (3 * log2(size)));
ASSERT_TRUE(height <= 2 * log2(size + 1));
}
TEST_F(IntervalTreeTest, Remove) {
@ -82,7 +83,7 @@ TEST_F(IntervalTreeTest, Clear) {
TEST_F(IntervalTreeTest, Find) {
for (uint32_t i = 0; i < HIGH; i += 0x1000) {
// manually generate a list of results
std::set<TestTree::Node *> expected;
std::set<TestNode *> expected;
for (auto n : nodes) {
if (i < n->low || i > n->high) {
@ -94,7 +95,7 @@ TEST_F(IntervalTreeTest, Find) {
// query the tree for nodes and compare with the expected results
int found = 0;
TestTree::Node *n = intervals.Find(i, i);
TestNode *n = intervals.Find(i, i);
while (n) {
// validate that it's in the expected set
@ -102,7 +103,7 @@ TEST_F(IntervalTreeTest, Find) {
ASSERT_NE(it, expected.end());
found++;
// remove from nodes so the node isn't expected by the next loop iteration
// remove from nodes so the node isn't expected by the next loop
auto it2 = nodes.find(n);
ASSERT_NE(it2, nodes.end());
nodes.erase(it2);
@ -122,7 +123,7 @@ TEST_F(IntervalTreeTest, Find) {
TEST_F(IntervalTreeTest, Iterate) {
for (uint32_t i = 0; i < HIGH; i += 0x1000) {
// manually generate a list of expected nodes
std::set<TestTree::Node *> expected;
std::set<TestNode *> expected;
for (auto n : nodes) {
if (i < n->low || i > n->high) {
@ -133,9 +134,9 @@ TEST_F(IntervalTreeTest, Iterate) {
}
// query the tree for nodes
std::set<TestTree::Node *> results;
std::set<TestNode *> results;
intervals.Iterate(i, i, [&](const TestTree &tree, TestTree::Node *node) {
intervals.Iterate(i, i, [&](const TestTree &tree, TestNode *node) {
results.insert(node);
});