]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
common/: introduce intrusive_lru
authorSamuel Just <sjust@redhat.com>
Fri, 4 Oct 2019 05:22:09 +0000 (22:22 -0700)
committerSamuel Just <sjust@redhat.com>
Tue, 3 Dec 2019 05:35:36 +0000 (21:35 -0800)
Signed-off-by: Samuel Just <sjust@redhat.com>
src/common/intrusive_lru.h [new file with mode: 0644]
src/test/common/CMakeLists.txt
src/test/common/test_intrusive_lru.cc [new file with mode: 0644]

diff --git a/src/common/intrusive_lru.h b/src/common/intrusive_lru.h
new file mode 100644 (file)
index 0000000..31bbde3
--- /dev/null
@@ -0,0 +1,178 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/intrusive/set.hpp>
+#include <boost/intrusive/list.hpp>
+
+namespace ceph::common {
+
+/**
+ * intrusive_lru: lru implementation with embedded map and list hook
+ *
+ * Note, this implementation currently is entirely thread-unsafe.
+ */
+
+template <typename K, typename V, typename VToK>
+struct intrusive_lru_config {
+  using key_type = K;
+  using value_type = V;
+  using key_of_value = VToK;
+};
+
+template <typename Config>
+class intrusive_lru;
+
+template <typename Config>
+class intrusive_lru_base;
+
+template <typename Config>
+void intrusive_ptr_add_ref(intrusive_lru_base<Config> *p);
+
+template <typename Config>
+void intrusive_ptr_release(intrusive_lru_base<Config> *p);
+
+
+template <typename Config>
+class intrusive_lru_base {
+  unsigned use_count = 0;
+
+  // null if unreferenced
+  intrusive_lru<Config> *lru = nullptr;
+
+public:
+  boost::intrusive::set_member_hook<> set_hook;
+  boost::intrusive::list_member_hook<> list_hook;
+
+  using Ref = boost::intrusive_ptr<typename Config::value_type>;
+  using lru_t = intrusive_lru<Config>;
+
+  friend intrusive_lru<Config>;
+  friend void intrusive_ptr_add_ref<>(intrusive_lru_base<Config> *);
+  friend void intrusive_ptr_release<>(intrusive_lru_base<Config> *);
+
+  virtual ~intrusive_lru_base() {}
+};
+
+template <typename Config>
+class intrusive_lru {
+  using base_t = intrusive_lru_base<Config>;
+  using K = typename Config::key_type;
+  using T = typename Config::value_type;
+  using TRef = typename base_t::Ref;
+
+  using lru_set_option_t = boost::intrusive::member_hook<
+    base_t,
+    boost::intrusive::set_member_hook<>,
+    &base_t::set_hook>;
+
+  using VToK = typename Config::key_of_value;
+  struct VToKWrapped {
+    using type = typename VToK::type;
+    const type &operator()(const base_t &obc) {
+      return VToK()(static_cast<const T&>(obc));
+    }
+  };
+  using lru_set_t = boost::intrusive::set<
+    base_t,
+    lru_set_option_t,
+    boost::intrusive::key_of_value<VToKWrapped>
+    >;
+  lru_set_t lru_set;
+
+  using lru_list_t = boost::intrusive::list<
+    base_t,
+    boost::intrusive::member_hook<
+      base_t,
+      boost::intrusive::list_member_hook<>,
+      &base_t::list_hook>>;
+  lru_list_t unreferenced_list;
+
+  size_t lru_target_size = 0;
+
+  void evict() {
+    while (!unreferenced_list.empty() &&
+          lru_set.size() > lru_target_size) {
+      auto &b = unreferenced_list.front();
+      assert(!b.lru);
+      unreferenced_list.pop_front();
+      lru_set.erase_and_dispose(
+       lru_set.iterator_to(b),
+       [](auto *p) { delete p; }
+      );
+    }
+  }
+
+  void access(base_t &b) {
+    if (b.lru)
+      return;
+    unreferenced_list.erase(lru_list_t::s_iterator_to(b));
+    b.lru = this;
+  }
+
+  void insert(base_t &b) {
+    assert(!b.lru);
+    lru_set.insert(b);
+    b.lru = this;
+    evict();
+  }
+
+  void unreferenced(base_t &b) {
+    assert(b.lru);
+    unreferenced_list.push_back(b);
+    b.lru = nullptr;
+    evict();
+  }
+
+public:
+  /**
+   * Returns the TRef corresponding to k if it exists or
+   * creates it otherwise.  Return is:
+   * std::pair(reference_to_val, found)
+   */
+  std::pair<TRef, bool> get_or_create(const K &k) {
+    typename lru_set_t::insert_commit_data icd;
+    auto [iter, missing] = lru_set.insert_check(
+      k,
+      icd);
+    if (missing) {
+      auto ret = new T(k);
+      lru_set.insert_commit(*ret, icd);
+      insert(*ret);
+      return {TRef(ret), false};
+    } else {
+      access(*iter);
+      return {TRef(static_cast<T*>(&*iter)), true};
+    }
+  }
+
+  void set_target_size(size_t target_size) {
+    lru_target_size = target_size;
+    evict();
+  }
+
+  friend void intrusive_ptr_add_ref<>(intrusive_lru_base<Config> *);
+  friend void intrusive_ptr_release<>(intrusive_lru_base<Config> *);
+};
+
+template <typename Config>
+void intrusive_ptr_add_ref(intrusive_lru_base<Config> *p) {
+  assert(p);
+  assert(p->lru);
+  p->use_count++;
+}
+
+template <typename Config>
+void intrusive_ptr_release(intrusive_lru_base<Config> *p) {
+  assert(p);
+  assert(p->use_count > 0);
+  --p->use_count;
+  if (p->use_count == 0) {
+    p->lru->unreferenced(*p);
+  }
+}
+
+
+}
index a3074d4319f44b454ffe3e6740ba01b8999696ac..f5c23649d218f3f46424f61512829d83d8751320 100644 (file)
@@ -151,6 +151,13 @@ add_executable(unittest_lru
 add_ceph_unittest(unittest_lru)
 target_link_libraries(unittest_lru ceph-common)
 
+# unittest_intrusive_lru
+add_executable(unittest_intrusive_lru
+  test_intrusive_lru.cc
+  )
+add_ceph_unittest(unittest_intrusive_lru)
+target_link_libraries(unittest_intrusive_lru ceph-common)
+
 # unittest_crc32c
 add_executable(unittest_crc32c
   test_crc32c.cc
diff --git a/src/test/common/test_intrusive_lru.cc b/src/test/common/test_intrusive_lru.cc
new file mode 100644 (file)
index 0000000..f242bdb
--- /dev/null
@@ -0,0 +1,149 @@
+// -*- 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 "common/intrusive_lru.h"
+
+template <typename TestLRUItem>
+struct item_to_unsigned {
+  using type = unsigned;
+  const type &operator()(const TestLRUItem &item) {
+    return item.key;
+  }
+};
+
+struct TestLRUItem : public ceph::common::intrusive_lru_base<
+  ceph::common::intrusive_lru_config<
+    unsigned, TestLRUItem, item_to_unsigned<TestLRUItem>>> {
+  unsigned key = 0;
+  int value = 0;
+  bool seen = false;
+
+  TestLRUItem(unsigned key) : key(key) {}
+};
+
+class LRUTest : public TestLRUItem::lru_t {
+public:
+  auto add(unsigned int key, int value) {
+    auto [ref, key_existed] = get_or_create(key);
+    if (!key_existed) {
+      ref->value = value;
+    }
+    return std::pair(ref, key_existed);
+  }
+};
+
+TEST(LRU, add_immediate_evict) {
+  LRUTest cache;
+  unsigned int key = 1;
+  int value1 = 2;
+  int value2 = 3;
+  {
+    auto [ref, existed] = cache.add(key, value1);
+    ASSERT_TRUE(ref);
+    ASSERT_EQ(value1, ref->value);
+    ASSERT_FALSE(existed);
+  }
+  {
+    auto [ref2, existed] = cache.add(key, value2);
+    ASSERT_EQ(value2, ref2->value);
+    ASSERT_FALSE(existed);
+  }
+}
+
+TEST(LRU, lookup_lru_size) {
+  LRUTest cache;
+  int key = 1;
+  int value = 1;
+  cache.set_target_size(1);
+  {
+    auto [ref, existed] = cache.add(key, value);
+    ASSERT_TRUE(ref);
+    ASSERT_FALSE(existed);
+  }
+  {
+    auto [ref, existed] = cache.add(key, value);
+    ASSERT_TRUE(ref);
+    ASSERT_TRUE(existed);
+  }
+  cache.set_target_size(0);
+  auto [ref2, existed2] = cache.add(key, value);
+  ASSERT_TRUE(ref2);
+  ASSERT_FALSE(existed2);
+  {
+    auto [ref, existed] = cache.add(key, value);
+    ASSERT_TRUE(ref);
+    ASSERT_TRUE(existed);
+  }
+}
+
+TEST(LRU, eviction) {
+  const unsigned SIZE = 3;
+  LRUTest cache;
+  cache.set_target_size(SIZE);
+
+  for (unsigned i = 0; i < SIZE; ++i) {
+    auto [ref, existed] = cache.add(i, i);
+    ASSERT_TRUE(ref && !existed);
+  }
+
+  {
+    auto [ref, existed] = cache.add(0, 0);
+    ASSERT_TRUE(ref && existed);
+  }
+
+  for (unsigned i = SIZE; i < (2*SIZE) - 1; ++i) {
+    auto [ref, existed]  = cache.add(i, i);
+    ASSERT_TRUE(ref && !existed);
+  }
+
+  {
+    auto [ref, existed] = cache.add(0, 0);
+    ASSERT_TRUE(ref && existed);
+  }
+
+  for (unsigned i = 1; i < SIZE; ++i) {
+    auto [ref, existed] = cache.add(i, i);
+    ASSERT_TRUE(ref && !existed);
+  }
+}
+
+TEST(LRU, eviction_live_ref) {
+  const unsigned SIZE = 3;
+  LRUTest cache;
+  cache.set_target_size(SIZE);
+
+  auto [live_ref, existed2] = cache.add(1, 1);
+  ASSERT_TRUE(live_ref && !existed2);
+
+  for (unsigned i = 0; i < SIZE; ++i) {
+    auto [ref, existed] = cache.add(i, i);
+    ASSERT_TRUE(ref);
+    if (i == 1) {
+      ASSERT_TRUE(existed);
+    } else {
+      ASSERT_FALSE(existed);
+    }
+  }
+
+  {
+    auto [ref, existed] = cache.add(0, 0);
+    ASSERT_TRUE(ref && existed);
+  }
+
+  for (unsigned i = SIZE; i < (2*SIZE) - 1; ++i) {
+    auto [ref, existed]  = cache.add(i, i);
+    ASSERT_TRUE(ref && !existed);
+  }
+
+  for (unsigned i = 0; i < SIZE; ++i) {
+    auto [ref, existed] = cache.add(i, i);
+    ASSERT_TRUE(ref);
+    if (i == 1) {
+      ASSERT_TRUE(existed);
+    } else {
+      ASSERT_FALSE(existed);
+    }
+  }
+}