]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
crimson/common: add fixed_kv_node_layout with tests
authorSamuel Just <sjust@redhat.com>
Mon, 4 May 2020 18:51:21 +0000 (11:51 -0700)
committerSamuel Just <sjust@redhat.com>
Tue, 2 Jun 2020 23:56:41 +0000 (16:56 -0700)
Signed-off-by: Samuel Just <sjust@redhat.com>
src/crimson/common/fixed_kv_node_layout.h [new file with mode: 0644]
src/test/crimson/CMakeLists.txt
src/test/crimson/test_fixed_kv_node_layout.cc [new file with mode: 0644]

diff --git a/src/crimson/common/fixed_kv_node_layout.h b/src/crimson/common/fixed_kv_node_layout.h
new file mode 100644 (file)
index 0000000..874edb5
--- /dev/null
@@ -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 <iostream>
+
+#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<ceph_le32, KINT, VINT>;
+  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<K>::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<char *>(node->get_key_ptr() + offset);
+    }
+
+    char *get_val_ptr() {
+      return reinterpret_cast<char *>(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;
+  }
+};
+
+}
index 786038c069c7bdf9a490478bc9734c750fc3239a..f20b496ec936a9a5f5f2dda06f152191b2a4913c 100644 (file)
@@ -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 (file)
index 0000000..2ca3d3a
--- /dev/null
@@ -0,0 +1,255 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <stdio.h>
+#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<uint32_t>::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<uint32_t>::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<uint32_t>::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<uint32_t>::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<uint32_t>::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<uint32_t>::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<uint32_t>::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);
+}