From: Samuel Just Date: Mon, 4 May 2020 18:51:21 +0000 (-0700) Subject: crimson/common: add fixed_kv_node_layout with tests X-Git-Tag: wip-pdonnell-testing-20200918.022351~1104^2~3 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=58afe444070d7d8b50b4ff26b58fc2b6fc76d676;p=ceph-ci.git crimson/common: add fixed_kv_node_layout with tests Signed-off-by: Samuel Just --- diff --git a/src/crimson/common/fixed_kv_node_layout.h b/src/crimson/common/fixed_kv_node_layout.h new file mode 100644 index 00000000000..874edb5744a --- /dev/null +++ b/src/crimson/common/fixed_kv_node_layout.h @@ -0,0 +1,346 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#pragma once + +#include + +#include "include/byteorder.h" + +#include "crimson/common/layout.h" + +namespace crimson::common { + +/** + * FixedKVNodeLayout + * + * Reusable implementation of a fixed size block mapping + * K -> V with internal representations KINT and VINT. + * + * Uses absl::container_internal::Layout for the actual memory layout. + * + * The primary interface exposed is centered on the fixed_node_iter_t + * and related methods. + * + * Also included are helpers for doing splits and merges as for a btree. + */ +template < + size_t CAPACITY, + typename K, + typename KINT, + typename V, + typename VINT> +class FixedKVNodeLayout { + char *buf = nullptr; + + using L = absl::container_internal::Layout; + static constexpr L layout{1, CAPACITY, CAPACITY}; + +public: + struct fixed_node_iter_t { + friend class FixedKVNodeLayout; + FixedKVNodeLayout *node; + uint16_t offset; + + fixed_node_iter_t( + FixedKVNodeLayout *parent, + uint16_t offset) : node(parent), offset(offset) {} + + fixed_node_iter_t(const fixed_node_iter_t &) = default; + fixed_node_iter_t(fixed_node_iter_t &&) = default; + fixed_node_iter_t &operator=(const fixed_node_iter_t &) = default; + fixed_node_iter_t &operator=(fixed_node_iter_t &&) = default; + + // Work nicely with for loops without requiring a nested type. + fixed_node_iter_t &operator*() { return *this; } + fixed_node_iter_t *operator->() { return this; } + + fixed_node_iter_t operator++(int) { + auto ret = *this; + ++offset; + return ret; + } + + fixed_node_iter_t &operator++() { + ++offset; + return *this; + } + + uint16_t operator-(const fixed_node_iter_t &rhs) const { + assert(rhs.node == node); + return offset - rhs.offset; + } + + fixed_node_iter_t operator+(uint16_t off) const { + return fixed_node_iter_t( + node, + offset + off); + } + fixed_node_iter_t operator-(uint16_t off) const { + return fixed_node_iter_t( + node, + offset - off); + } + + bool operator==(const fixed_node_iter_t &rhs) const { + assert(node == rhs.node); + return rhs.offset == offset; + } + + bool operator!=(const fixed_node_iter_t &rhs) const { + return !(*this == rhs); + } + + K get_key() const { + return K(node->get_key_ptr()[offset]); + } + + void set_key(K _lb) { + KINT lb; + lb = _lb; + node->get_key_ptr()[offset] = lb; + } + + K get_next_key_or_max() const { + auto next = *this + 1; + if (next == node->end()) + return std::numeric_limits::max(); + else + return next->get_key(); + } + + V get_val() const { + return V(node->get_val_ptr()[offset]); + }; + + void set_val(V val) { + node->get_val_ptr()[offset] = VINT(val); + } + + bool contains(K addr) { + return (get_key() <= addr) && (get_next_key_or_max() > addr); + } + + uint16_t get_offset() const { + return offset; + } + + private: + char *get_key_ptr() { + return reinterpret_cast(node->get_key_ptr() + offset); + } + + char *get_val_ptr() { + return reinterpret_cast(node->get_val_ptr() + offset); + } + }; + +public: + FixedKVNodeLayout(char *buf) : + buf(buf) {} + + fixed_node_iter_t begin() { + return fixed_node_iter_t( + this, + 0); + } + + fixed_node_iter_t end() { + return fixed_node_iter_t( + this, + get_size()); + } + + fixed_node_iter_t iter_idx(uint16_t off) { + return fixed_node_iter_t( + this, + off); + } + + fixed_node_iter_t find(K l) { + auto ret = begin(); + for (; ret != end(); ++ret) { + if (ret->get_key() == l) + break; + } + return ret; + } + + fixed_node_iter_t get_split_pivot() { + return iter_idx(get_size() / 2); + } + +private: + KINT *get_key_ptr() { + return layout.template Pointer<1>(buf); + } + + VINT *get_val_ptr() { + return layout.template Pointer<2>(buf); + } + +public: + uint16_t get_size() const { + return *layout.template Pointer<0>(buf); + } + + void set_size(uint16_t size) { + *layout.template Pointer<0>(buf) = size; + } + + constexpr static size_t get_capacity() { + return CAPACITY; + } + + /** + * copy_from_foreign + * + * Copies entries from [from_src, to_src) to tgt. + * + * tgt and from_src must be from different nodes. + * from_src and to_src must be from the same node. + */ + static void copy_from_foreign( + fixed_node_iter_t tgt, + fixed_node_iter_t from_src, + fixed_node_iter_t to_src) { + assert(tgt->node != from_src->node); + assert(to_src->node == from_src->node); + memcpy( + tgt->get_val_ptr(), from_src->get_val_ptr(), + to_src->get_val_ptr() - from_src->get_val_ptr()); + memcpy( + tgt->get_key_ptr(), from_src->get_key_ptr(), + to_src->get_key_ptr() - from_src->get_key_ptr()); + } + + /** + * copy_from_local + * + * Copies entries from [from_src, to_src) to tgt. + * + * tgt, from_src, and to_src must be from the same node. + */ + static void copy_from_local( + fixed_node_iter_t tgt, + fixed_node_iter_t from_src, + fixed_node_iter_t to_src) { + assert(tgt->node == from_src->node); + assert(to_src->node == from_src->node); + memmove( + tgt->get_val_ptr(), from_src->get_val_ptr(), + to_src->get_val_ptr() - from_src->get_val_ptr()); + memmove( + tgt->get_key_ptr(), from_src->get_key_ptr(), + to_src->get_key_ptr() - from_src->get_key_ptr()); + } + + /** + * split_into + * + * Takes *this and splits its contents into left and right. + */ + K split_into( + FixedKVNodeLayout &left, + FixedKVNodeLayout &right) { + auto piviter = get_split_pivot(); + + left.copy_from_foreign(left.begin(), begin(), piviter); + left.set_size(piviter - begin()); + + right.copy_from_foreign(right.begin(), piviter, end()); + right.set_size(end() - piviter); + + return piviter->get_key(); + } + + /** + * merge_from + * + * Takes two nodes and copies their contents into *this. + * + * precondition: left.size() + right.size() < CAPACITY + */ + void merge_from( + FixedKVNodeLayout &left, + FixedKVNodeLayout &right) + { + copy_from_foreign( + end(), + left.begin(), + left.end()); + set_size(left.get_size()); + copy_from_foreign( + end(), + right.begin(), + right.end()); + set_size(left.get_size() + right.get_size()); + } + + /** + * balance_into_new_nodes + * + * Takes the contents of left and right and copies them into + * replacement_left and replacement_right such that in the + * event that the number of elements is odd the extra goes to + * the left side iff prefer_left. + */ + static K balance_into_new_nodes( + FixedKVNodeLayout &left, + FixedKVNodeLayout &right, + bool prefer_left, + FixedKVNodeLayout &replacement_left, + FixedKVNodeLayout &replacement_right) + { + auto total = left.get_size() + right.get_size(); + auto pivot_idx = (left.get_size() + right.get_size()) / 2; + if (total % 2 && prefer_left) { + pivot_idx++; + } + auto replacement_pivot = pivot_idx > left.get_size() ? + right.iter_idx(pivot_idx - left.get_size())->get_key() : + left.iter_idx(pivot_idx)->get_key(); + + if (pivot_idx < left.get_size()) { + replacement_left.copy_from_foreign( + replacement_left.end(), + left.begin(), + left.iter_idx(pivot_idx)); + replacement_left.set_size(pivot_idx); + + replacement_right.copy_from_foreign( + replacement_right.end(), + left.iter_idx(pivot_idx), + left.end()); + + replacement_right.set_size(left.get_size() - pivot_idx); + replacement_right.copy_from_foreign( + replacement_right.end(), + right.begin(), + right.end()); + replacement_right.set_size(total - pivot_idx); + } else { + replacement_left.copy_from_foreign( + replacement_left.end(), + left.begin(), + left.end()); + replacement_left.set_size(left.get_size()); + + replacement_left.copy_from_foreign( + replacement_left.end(), + right.begin(), + right.iter_idx(pivot_idx - left.get_size())); + replacement_left.set_size(pivot_idx); + + replacement_right.copy_from_foreign( + replacement_right.end(), + right.iter_idx(pivot_idx - left.get_size()), + right.end()); + replacement_right.set_size(total - pivot_idx); + } + + return replacement_pivot; + } +}; + +} diff --git a/src/test/crimson/CMakeLists.txt b/src/test/crimson/CMakeLists.txt index 786038c069c..f20b496ec93 100644 --- a/src/test/crimson/CMakeLists.txt +++ b/src/test/crimson/CMakeLists.txt @@ -55,4 +55,9 @@ add_executable(unittest_seastar_lru add_ceph_unittest(unittest_seastar_lru --memory 256M --smp 1) target_link_libraries(unittest_seastar_lru crimson GTest::Main) +add_executable(unittest_fixed_kv_node_layout + test_fixed_kv_node_layout.cc) +add_ceph_unittest(unittest_fixed_kv_node_layout) +target_link_libraries(unittest_seastar_lru crimson GTest::Main) + add_subdirectory(seastore) diff --git a/src/test/crimson/test_fixed_kv_node_layout.cc b/src/test/crimson/test_fixed_kv_node_layout.cc new file mode 100644 index 00000000000..2ca3d3a4932 --- /dev/null +++ b/src/test/crimson/test_fixed_kv_node_layout.cc @@ -0,0 +1,255 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include +#include "gtest/gtest.h" + +#include "crimson/common/fixed_kv_node_layout.h" + +using namespace crimson; +using namespace crimson::common; + +struct test_val_t { + uint32_t t1 = 0; + int32_t t2 = 0; + + bool operator==(const test_val_t &rhs) const { + return rhs.t1 == t1 && rhs.t2 == t2; + } +}; + +struct test_val_le_t { + ceph_le32 t1 = init_le32(0); + ceph_les32 t2 = init_les32(0); + + test_val_le_t() = default; + test_val_le_t(const test_val_le_t &) = default; + test_val_le_t(const test_val_t &nv) + : t1(init_le32(nv.t1)), t2(init_les32(nv.t2)) {} + + operator test_val_t() { + return test_val_t{t1, t2}; + } +}; + +constexpr size_t CAPACITY = 341; + +struct TestNode : FixedKVNodeLayout< + CAPACITY, + uint32_t, ceph_le32, + test_val_t, test_val_le_t> { + char buf[4096]; + TestNode() : FixedKVNodeLayout(buf) { + memset(buf, 0, sizeof(buf)); + } +}; + +TEST(FixedKVNodeTest, basic) { + auto node = TestNode(); + ASSERT_EQ(node.get_size(), 0); + node.set_size(1); + ASSERT_EQ(node.get_size(), 1); + + auto iter = node.begin(); + iter.set_key(1); + ASSERT_EQ(iter.get_key(), 1); + + auto val = test_val_t{ 1, 1 }; + iter.set_val(val); + ASSERT_EQ(val, iter.get_val()); + + ASSERT_EQ(std::numeric_limits::max(), iter.get_next_key_or_max()); +} + +TEST(FixedKVNodeTest, at_capacity) { + auto node = TestNode(); + ASSERT_EQ(CAPACITY, node.get_capacity()); + + ASSERT_EQ(node.get_size(), 0); + node.set_size(node.get_capacity()); + ASSERT_EQ(node.get_size(), CAPACITY); + + unsigned short num = 0; + for (auto &i : node) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + + num = 0; + for (auto &i : node) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < (CAPACITY - 1)) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } +} + +TEST(FixedKVNodeTest, split) { + auto node = TestNode(); + + ASSERT_EQ(node.get_size(), 0); + + node.set_size(CAPACITY); + + unsigned short num = 0; + for (auto &i : node) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + + auto split_left = TestNode(); + auto split_right = TestNode(); + node.split_into(split_left, split_right); + + ASSERT_EQ(split_left.get_size() + split_right.get_size(), CAPACITY); + num = 0; + for (auto &i : split_left) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < split_left.get_size() - 1) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } + for (auto &i : split_right) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < CAPACITY - 1) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } + ASSERT_EQ(num, CAPACITY); +} + + +TEST(FixedKVNodeTest, merge) { + auto node = TestNode(); + auto node2 = TestNode(); + + ASSERT_EQ(node.get_size(), 0); + ASSERT_EQ(node2.get_size(), 0); + + node.set_size(CAPACITY / 2); + node2.set_size(CAPACITY / 2); + + auto total = node.get_size() + node2.get_size(); + + unsigned short num = 0; + for (auto &i : node) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + for (auto &i : node2) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + + auto node_merged = TestNode(); + node_merged.merge_from(node, node2); + + ASSERT_EQ(node_merged.get_size(), total); + num = 0; + for (auto &i : node_merged) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < node_merged.get_size() - 1) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } + ASSERT_EQ(num, total); +} + +void run_balance_test(unsigned left, unsigned right, bool prefer_left) +{ + auto node = TestNode(); + auto node2 = TestNode(); + + ASSERT_EQ(node.get_size(), 0); + ASSERT_EQ(node2.get_size(), 0); + + node.set_size(left); + node2.set_size(right); + + auto total = left + right; + + unsigned short num = 0; + for (auto &i : node) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + for (auto &i : node2) { + i.set_key(num); + i.set_val({ num, num}); + ++num; + } + + auto node_balanced = TestNode(); + auto node_balanced2 = TestNode(); + TestNode::balance_into_new_nodes( + node, + node2, + prefer_left, + node_balanced, + node_balanced2); + + ASSERT_EQ(total, node_balanced.get_size() + node_balanced2.get_size()); + + if (total % 2) { + if (prefer_left) { + ASSERT_EQ(node_balanced.get_size(), node_balanced2.get_size() + 1); + } else { + ASSERT_EQ(node_balanced.get_size() + 1, node_balanced2.get_size()); + } + } else { + ASSERT_EQ(node_balanced.get_size(), node_balanced2.get_size()); + } + + num = 0; + for (auto &i: node_balanced) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < node_balanced.get_size() - 1) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } + for (auto &i: node_balanced2) { + ASSERT_EQ(i.get_key(), num); + ASSERT_EQ(i.get_val(), (test_val_t{num, num})); + if (num < total - 1) { + ASSERT_EQ(i.get_next_key_or_max(), num + 1); + } else { + ASSERT_EQ(std::numeric_limits::max(), i.get_next_key_or_max()); + } + ++num; + } +} + +TEST(FixedKVNodeTest, balanced) { + run_balance_test(CAPACITY / 2, CAPACITY, true); + run_balance_test(CAPACITY / 2, CAPACITY, false); + run_balance_test(CAPACITY, CAPACITY / 2, true); + run_balance_test(CAPACITY, CAPACITY / 2, false); + run_balance_test(CAPACITY - 1, CAPACITY / 2, true); + run_balance_test(CAPACITY / 2, CAPACITY - 1, false); + run_balance_test(CAPACITY / 2, CAPACITY / 2, false); +}