Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ jobs:
working-directory: ./build
run: ./louds_tree_tests

- name: Run BP Tree Tests
working-directory: ./build
run: ./bp_tree_tests

- name: Run DFUDS Tree Tests
working-directory: ./build
run: ./dfuds_tree_tests
Expand Down Expand Up @@ -96,6 +100,18 @@ jobs:
fi
exit $rc

- name: Run BP Tree Tests
working-directory: ./build
run: |
timeout 1800 sde-external-9.58.0-2025-06-16-lin/sde64 -icl -emu-xinuse 0 -- \
./bp_tree_tests --gtest_output=xml:bp_results.xml
rc=$?
if [ $rc -eq 124 ] && grep -q 'failures="0"' bp_results.xml 2>/dev/null; then
echo "SDE timed out during process teardown (known SDE/ASan issue) - all tests passed, treating as success"
exit 0
fi
exit $rc

- name: Run DFUDS Tree Tests
working-directory: ./build
run: |
Expand Down
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ if(PIXIE_TESTS)
gtest_main
${PIXIE_DIAGNOSTICS_LIBS})

add_executable(bp_tree_tests
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should be added to coverage/build-test workflows (but not for AVX-512).

src/tests/bp_tree_tests.cpp)
target_include_directories(bp_tree_tests
PUBLIC include)
target_link_libraries(bp_tree_tests
gtest
gtest_main
${PIXIE_DIAGNOSTICS_LIBS})

add_executable(dfuds_tree_tests
src/tests/dfuds_tree_tests.cpp)
target_include_directories(dfuds_tree_tests
Expand Down Expand Up @@ -239,6 +248,15 @@ if(PIXIE_BENCHMARKS)
benchmark_main
${PIXIE_DIAGNOSTICS_LIBS})

add_executable(bp_tree_benchmarks
src/benchmarks/bp_tree_benchmarks.cpp)
target_include_directories(bp_tree_benchmarks
PUBLIC include)
target_link_libraries(bp_tree_benchmarks
benchmark
benchmark_main
${PIXIE_DIAGNOSTICS_LIBS})

add_executable(dfuds_tree_benchmarks
src/benchmarks/dfuds_tree_benchmarks.cpp)
target_include_directories(dfuds_tree_benchmarks
Expand Down
7 changes: 7 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"benchmark_tests",
"test_rmm",
"louds_tree_tests",
"bp_tree_tests",
"dfuds_tree_tests",
"excess_positions_tests"
]
Expand All @@ -127,6 +128,7 @@
"benchmark_tests",
"test_rmm",
"louds_tree_tests",
"bp_tree_tests",
"dfuds_tree_tests",
"excess_positions_tests"
]
Expand All @@ -139,6 +141,7 @@
"benchmarks",
"bench_rmm",
"louds_tree_benchmarks",
"bp_tree_benchmarks",
"dfuds_tree_benchmarks",
"alignment_comparison",
"excess_positions_benchmarks"
Expand All @@ -153,6 +156,7 @@
"bench_rmm",
"bench_rmm_sdsl",
"louds_tree_benchmarks",
"bp_tree_benchmarks",
"dfuds_tree_benchmarks",
"alignment_comparison",
"excess_positions_benchmarks"
Expand All @@ -166,6 +170,7 @@
"benchmarks",
"bench_rmm",
"louds_tree_benchmarks",
"bp_tree_benchmarks",
"dfuds_tree_benchmarks",
"alignment_comparison",
"excess_positions_benchmarks"
Expand All @@ -188,6 +193,7 @@
"benchmark_tests",
"test_rmm",
"louds_tree_tests",
"bp_tree_tests",
"dfuds_tree_tests",
"excess_positions_tests"
]
Expand All @@ -201,6 +207,7 @@
"benchmark_tests",
"test_rmm",
"louds_tree_tests",
"bp_tree_tests",
"dfuds_tree_tests",
"excess_positions_tests"
]
Expand Down
171 changes: 171 additions & 0 deletions include/pixie/bp_tree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#pragma once

#include <pixie/rmm_tree.h>

#include <cstdint>

#include "utils.h"

namespace pixie {

/**
* @brief A tree class based on the balances parentheses (BP)
* representation
*/
class BPTree {
private:
const size_t num_bits_;
RmMTree rmm;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rmm_


public:
/**
* @brief A node class of BP tree
*/
struct Node {
size_t number;
size_t pos;

/**
* @brief A node class of BP tree
*/
Node(size_t node_number, size_t bp_pos)
: number(node_number), pos(bp_pos) {}
};

/**
* @brief Constructor from an external array of uint64_t
*/
explicit BPTree(const std::vector<std::uint64_t>& words, size_t tree_size)
: num_bits_(2 * tree_size), rmm(words, 2 * tree_size) {}

/**
* @brief Returns the root node
*/
Node root() const { return Node(0, 0); }

/**
* @brief Returns the size of the tree
*/
size_t size() const { return num_bits_ / 2; }

/**
* @brief Indicates if @p node is a leaf
*/
bool is_leaf(const Node& node) const {
return (node.pos + 2 == num_bits_) or rmm.bit(node.pos + 1) == 0;
}

/**
* @brief Indicates if @p node is a root
*/
bool is_root(const Node& node) { return node.number == 0; }

/**
* @brief Returns the number of children of a @p node
* this method has O(d) time complexity!
*
* TODO try make this faster
*/
size_t degree(const Node& node) const {
if (is_leaf(node)) {
return 0;
}
Node child = first_child(node);
size_t child_count = 1;
while (true) {
if (is_last_child(child)) {
return child_count;
}
child = next_sibling(child);
child_count++;
}
}

/**
* @brief Returns first child of a @p node
*/
Node first_child(const Node& node) const {
size_t pos = node.pos + 1;
size_t num = node.number + 1;
return Node(num, pos);
}

/**
* @brief Returns the i-th child of @p node
* Indexing starts at 0
* this method has O(i) time complexity!
*
* TODO try make this faster
*/
Node child(const Node& node, size_t i) const {
Node child = first_child(node);
while (i--) {
child = next_sibling(child);
}
return child;
}

/**
* @brief Returns the parent of a @p node if @p node is not root,
* else returns root
*/
Node parent(const Node& node) const {
if (node.number == 0) {
return root();
}
size_t pos = rmm.enclose(node.pos);
size_t num = rmm.rank1(pos);
return Node(num, pos);
}

/**
* @brief Indicates if @p node is last child
*/
bool is_last_child(const Node& node) const {
size_t end = rmm.close(node.pos);

return end + 2 >= num_bits_ or rmm.bit(end + 1) == 0;
}

/**
* @brief Returns next sibling of a @p node
*/
Node next_sibling(const Node& node) const {
size_t pos = rmm.close(node.pos) + 1;
size_t num = rmm.rank1(pos + 1) - 1;
return Node(num, pos);
}
};

std::vector<uint64_t> adj_to_bp(size_t tree_size,
const std::vector<std::vector<size_t>>& adj) {
size_t bp_size = tree_size * 2;
std::vector<uint64_t> bp((bp_size + 63) / 64, 0);
std::vector<std::pair<size_t, size_t>> stack;
stack.push_back(std::make_pair(0, 0));
size_t pos = 0;
bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63));
while (!stack.empty()) {
auto& [v, p] = stack.back();
p++;
if (p >= adj[v].size()) {
pos++;
stack.pop_back();
continue;
}
pos++;
bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63));
stack.push_back(std::make_pair(adj[v][p], 0));
}
return bp;
}

bool operator==(const AdjListNode& a, const BPTree::Node& b) {
return a.number == b.number;
}

bool operator==(const BPTree::Node& b, const AdjListNode& a) {
return a.number == b.number;
}

} // namespace pixie
2 changes: 1 addition & 1 deletion include/pixie/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ std::vector<std::vector<size_t>> bfs_order(
std::queue<std::pair<size_t, size_t>> q;
bfs_adj[0].push_back(0);
q.push({0, 0});
int cnt = 1;
size_t cnt = 1;
while (!q.empty()) {
size_t old_v = q.front().first;
size_t cur_v = q.front().second;
Expand Down
1 change: 1 addition & 0 deletions scripts/coverage_report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ cmake --build --preset coverage
"${BUILD_DIR}/unittests"
"${BUILD_DIR}/excess_positions_tests"
"${BUILD_DIR}/louds_tree_tests"
"${BUILD_DIR}/bp_tree_tests"
"${BUILD_DIR}/dfuds_tree_tests"
"${BUILD_DIR}/test_rmm"

Expand Down
66 changes: 66 additions & 0 deletions src/benchmarks/bp_tree_benchmarks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <benchmark/benchmark.h>
#include <pixie/bp_tree.h>
#include <pixie/utils.h>

#include <random>

using Node = pixie::BpTree::Node;
using pixie::BpTree;

/**
* DFS with O(1) extra memory
*/
static void BM_BpTreeDFS(benchmark::State& state) {
size_t tree_size = state.range(0);
std::mt19937_64 rng(42);

for (auto _ : state) {
state.PauseTiming();
std::vector<std::vector<size_t>> adj = generate_random_tree(tree_size, rng);
adj = dfs_order(tree_size, adj);
std::vector<uint64_t> bp = adj_to_bp(tree_size, adj);
BpTree tree(bp, tree_size);

Node cur = tree.root();
bool above = 1;

state.ResumeTiming();

benchmark::DoNotOptimize(cur);

while (true) {
if (above) {
if (tree.is_leaf(cur)) {
above = 0;
} else {
cur = tree.first_child(cur);
}
benchmark::DoNotOptimize(cur);
} else {
if (tree.is_last_child(cur)) {
cur = tree.parent(cur);
if (tree.is_root(cur)) {
break;
}
benchmark::DoNotOptimize(cur);
} else {
cur = tree.next_sibling(cur);
above = 1;
benchmark::DoNotOptimize(cur);
}
}
}
}
}

BENCHMARK(BM_BpTreeDFS)
->ArgNames({"tree_size"})
->RangeMultiplier(2)
->Range(1ull << 8, 1ull << 18)
->Iterations(100);

BENCHMARK(BM_BpTreeDFS)
->ArgNames({"tree_size"})
->RangeMultiplier(2)
->Range(1ull << 18, 1ull << 26)
->Iterations(10);
Loading
Loading