Skip to content

Commit

Permalink
Add multithreading to , add test, remove stack size debug code
Browse files Browse the repository at this point in the history
  • Loading branch information
greg7mdp committed Mar 29, 2024
1 parent d2f1c92 commit 12e6a53
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 31 deletions.
14 changes: 5 additions & 9 deletions libraries/chain/include/eosio/chain/incremental_merkle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ namespace eosio::chain {
class incremental_merkle_tree {
public:
void append(const digest_type& digest) {
char c;
assert(trees.size() == detail::popcount(mask));
_append(digest, trees.end(), 0, &c);
_append(digest, trees.end(), 0);
assert(trees.size() == detail::popcount(mask));
}

Expand All @@ -22,8 +21,6 @@ class incremental_merkle_tree {
return _get_root(0);
};

int64_t max_stack_depth = 0;

private:
friend struct fc::reflector<incremental_merkle_tree>;
using vec_it = std::vector<digest_type>::iterator;
Expand All @@ -35,13 +32,11 @@ class incremental_merkle_tree {
digest_type _get_root(size_t idx) const {
if (idx + 1 == trees.size())
return trees[idx];
return detail::hash_combine(trees[idx], _get_root(idx + 1));
return detail::hash_combine(trees[idx], _get_root(idx + 1)); // log2 recursion OK
}

// slot points to the current insertion point. *(slot-1) is the digest for the first bit set >= idx
void _append(const digest_type& digest, vec_it slot, size_t idx, const char *p) {
char c;
max_stack_depth = std::max(max_stack_depth, p - &c);
void _append(const digest_type& digest, vec_it slot, size_t idx) {
if (is_bit_set(idx)) {
assert(!trees.empty());
if (!is_bit_set(idx+1)) {
Expand All @@ -55,7 +50,8 @@ class incremental_merkle_tree {
clear_bit(idx+1);
digest_type d = detail::hash_combine(*(slot-2), detail::hash_combine(*(slot-1), digest));
trees.erase(slot-2, slot);
_append(d, slot-2, idx+2, p);
_append(d, slot-2, idx+2); // log2 recursion OK, uses less than 5KB stack space for 32M digests
// appended (or 0.25% of default 2MB thread stack size on Ubuntu)
}
} else {
trees.insert(slot, digest);
Expand Down
32 changes: 26 additions & 6 deletions libraries/chain/include/eosio/chain/merkle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include <eosio/chain/types.hpp>
#include <fc/io/raw.hpp>
#include <bit>
#include <array>
#include <future>

namespace eosio::chain {

Expand All @@ -20,8 +22,11 @@ inline digest_type hash_combine(const digest_type& a, const digest_type& b) {
}

// does not overwrite passed sequence
// ----------------------------------
template <class It>
//
// log2 recursion OK, uses less than 5KB stack space for 32M digests
// appended (or 0.25% of default 2MB thread stack size on Ubuntu)
// -----------------------------------------------------------------
template <class It, bool async = false>
requires std::is_same_v<std::decay_t<typename std::iterator_traits<It>::value_type>, digest_type>
inline digest_type calculate_merkle_pow2(const It& start, const It& end) {
auto size = end - start;
Expand All @@ -31,8 +36,23 @@ inline digest_type calculate_merkle_pow2(const It& start, const It& end) {
if (size == 2)
return hash_combine(start[0], start[1]);
else {
auto mid = start + size / 2;
return hash_combine(calculate_merkle_pow2(start, mid), calculate_merkle_pow2(mid, end));
if (async && size >= 4096) { // below 4096, starting async threads is overkill
std::array<std::future<digest_type>, 4> fut; // size dictates the number of threads (must be power of two)
size_t slice_size = size / fut.size();

for (size_t i=0; i<fut.size(); ++i)
fut[i] = std::async(std::launch::async, calculate_merkle_pow2<It>,
start + slice_size * i, start + slice_size * (i+1));

std::array<digest_type, fut.size()> res;
for (size_t i=0; i<fut.size(); ++i)
res[i] = fut[i].get();

return calculate_merkle_pow2(res.begin(), res.end());
} else {
auto mid = start + size / 2;
return hash_combine(calculate_merkle_pow2(start, mid), calculate_merkle_pow2(mid, end));
}
}
}

Expand All @@ -46,10 +66,10 @@ inline digest_type calculate_merkle(const It& start, const It& end) {

auto midpoint = detail::bit_floor(size);
if (size == midpoint)
return calculate_merkle_pow2(start, end);
return calculate_merkle_pow2<It, true>(start, end);

auto mid = start + midpoint;
return hash_combine(calculate_merkle_pow2(start, mid), calculate_merkle(mid, end));
return hash_combine(calculate_merkle_pow2<It, true>(start, mid), calculate_merkle(mid, end));
}

}
Expand Down
67 changes: 51 additions & 16 deletions unittests/merkle_tree_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,6 @@ BOOST_AUTO_TEST_CASE(consistency_over_large_range) {
}
}

BOOST_AUTO_TEST_CASE(stack_check) {
constexpr size_t num_digests = 1024ull;

std::vector<digest_type> digests = create_test_digests(num_digests);
incremental_merkle_tree tree;
for (size_t j=0; j<num_digests; ++j) {
tree.append(digests[j]);
if (j == num_digests - 1) {
BOOST_CHECK_LE(tree.max_stack_depth, 2000);
std::cout << "max_stack_depth = " << tree.max_stack_depth << " bytes\n";
}
}
}

class stopwatch {
public:
stopwatch(std::string msg) : _msg(std::move(msg)) { _start = clock::now(); }
Expand All @@ -273,10 +259,10 @@ class stopwatch {
point _start;
};

BOOST_AUTO_TEST_CASE(perf_test) {
BOOST_AUTO_TEST_CASE(perf_test_one_large) {
auto perf_test = [](const std::string& type, auto&& incr_tree, auto&& calc_fn) {
using namespace std::string_literals;
constexpr size_t num_digests = 1024ull * 1024ull;
constexpr size_t num_digests = 1000ull * 1000ull; // don't use exact powers of 2 as it is a special case

std::vector<digest_type> digests = create_test_digests(num_digests);
deque<digest_type> deq { digests.begin(), digests.end() };
Expand Down Expand Up @@ -308,4 +294,53 @@ BOOST_AUTO_TEST_CASE(perf_test) {
}


BOOST_AUTO_TEST_CASE(perf_test_many_small) {

auto perf_test = [](const std::string& type, auto&& incr_tree, auto&& calc_fn) {
using namespace std::string_literals;
constexpr size_t num_digests = 10000; // don't use exact powers of 2 as it is a special case
constexpr size_t num_runs = 100;

std::vector<digest_type> digests = create_test_digests(num_digests);
deque<digest_type> deq { digests.begin(), digests.end() };

deque<digest_type> results(num_runs);

auto incr = [&]() {
auto work_tree = incr_tree;
for (const auto& d : digests)
work_tree.append(d);
return work_tree.get_root();
};

auto calc = [&]() { return calc_fn(deq); };

auto incr_root = [&]() {
stopwatch s("time for "s + type + " incremental_merkle: ");
for (auto& r : results)
r = incr();
return calc_fn(results);
}();

auto calc_root = [&]() {
stopwatch s("time for "s + type + " calculate_merkle: ");
for (auto& r : results)
r = calc();
return calc_fn(results);
}();

return std::make_pair(incr_root, calc_root);
};

{
auto [incr_root, calc_root] = perf_test("new", incremental_merkle_tree(), calculate_merkle);
BOOST_CHECK_EQUAL(incr_root, calc_root);
}

{
auto [incr_root, calc_root] = perf_test("legacy", incremental_merkle_tree_legacy(), calculate_merkle_legacy);
BOOST_CHECK_EQUAL(incr_root, calc_root);
}
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 12e6a53

Please sign in to comment.