A core-cached shared_ptr
authorGiuseppe Ottaviano <ott@fb.com>
Wed, 24 May 2017 05:52:12 +0000 (22:52 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 24 May 2017 06:04:53 +0000 (23:04 -0700)
Summary:
This class serves a similar use case to `ReadMostlySharedPtr`, but with two differences:

- It returns actual `shared_ptr`s, so it can be used as a drop-in replacement.
- Its memory utilization is constant, independent of the number of threads that use it.

Also, the code is much simpler. On the other hand, it is about 4x slower (but it still scales linearly in the number of concurrent threads acquiring the reference).

Reviewed By: djwatson

Differential Revision: D5104730

fbshipit-source-id: 8c18b635e0390394b06417b6df8b790e4bd2d90d

folly/Enumerate.h
folly/Makefile.am
folly/concurrency/CoreCachedSharedPtr.h [new file with mode: 0644]
folly/concurrency/test/CoreCachedSharedPtrTest.cpp [new file with mode: 0644]
folly/experimental/test/ReadMostlySharedPtrBenchmark.cpp

index 9efecfd2b83b5e422663bc2b81ba6ee7b10b8fdb..94bcecefbd647273a059ee2418a524a162fd44ac 100644 (file)
@@ -21,7 +21,7 @@
 
 #include <folly/portability/SysTypes.h>
 
-/*
+/**
  * Similar to Python's enumerate(), folly::enumerate() can be used to
  * iterate a range with a for-range loop, and it also allows to
  * retrieve the count of iterations so far.
index f03ceda976e310da0125c6fd86392e66caa9cb6f..3993b45fa0254d67e64acbde751c3e1c8ff5fed7 100644 (file)
@@ -58,6 +58,7 @@ nobase_follyinclude_HEADERS = \
        CppAttributes.h \
        CpuId.h \
        CPortability.h \
+       concurrency/CoreCachedSharedPtr.h \
        detail/AtomicHashUtils.h \
        detail/AtomicUnorderedMapUtils.h \
        detail/AtomicUtils.h \
diff --git a/folly/concurrency/CoreCachedSharedPtr.h b/folly/concurrency/CoreCachedSharedPtr.h
new file mode 100644 (file)
index 0000000..7f5b9a9
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <memory>
+
+#include <folly/Enumerate.h>
+#include <folly/detail/CacheLocality.h>
+
+namespace folly {
+
+/**
+ * This class creates core-local caches for a given shared_ptr, to
+ * mitigate contention when acquiring/releasing it.
+ *
+ * It has the same thread-safety guarantees as shared_ptr: it is safe
+ * to concurrently call get(), but reset()s must be synchronized with
+ * reads and other resets().
+ *
+ * @author Giuseppe Ottaviano <ott@fb.com>
+ */
+template <class T, size_t kNumSlots = 64>
+class CoreCachedSharedPtr {
+ public:
+  explicit CoreCachedSharedPtr(const std::shared_ptr<T>& p = nullptr) {
+    reset(p);
+  }
+
+  void reset(const std::shared_ptr<T>& p = nullptr) {
+    for (auto& slot : slots_) {
+      auto holder = std::make_shared<Holder>(p);
+      slot = std::shared_ptr<T>(holder, p.get());
+    }
+  }
+
+  std::shared_ptr<T> get() const {
+    return slots_[detail::AccessSpreader<>::current(kNumSlots)];
+  }
+
+ private:
+  template <class, size_t>
+  friend class CoreCachedWeakPtr;
+
+  // Space the Holders by a cache line, so their control blocks (which
+  // are adjacent to the slots thanks to make_shared()) will also be
+  // spaced.
+  struct FOLLY_ALIGN_TO_AVOID_FALSE_SHARING Holder {
+    explicit Holder(std::shared_ptr<T> p) : ptr(std::move(p)) {}
+    std::shared_ptr<T> ptr;
+  };
+
+  std::array<std::shared_ptr<T>, kNumSlots> slots_;
+};
+
+template <class T, size_t kNumSlots = 64>
+class CoreCachedWeakPtr {
+ public:
+  explicit CoreCachedWeakPtr(const CoreCachedSharedPtr<T, kNumSlots>& p) {
+    for (auto slot : folly::enumerate(slots_)) {
+      *slot = p.slots_[slot.index];
+    }
+  }
+
+  std::weak_ptr<T> get() const {
+    return slots_[detail::AccessSpreader<>::current(kNumSlots)];
+  }
+
+ private:
+  std::array<std::weak_ptr<T>, kNumSlots> slots_;
+};
+
+} // namespace
diff --git a/folly/concurrency/test/CoreCachedSharedPtrTest.cpp b/folly/concurrency/test/CoreCachedSharedPtrTest.cpp
new file mode 100644 (file)
index 0000000..909a63b
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <folly/Benchmark.h>
+#include <folly/Portability.h>
+#include <folly/concurrency/CoreCachedSharedPtr.h>
+#include <folly/portability/GTest.h>
+
+TEST(CoreCachedSharedPtr, Basic) {
+  auto p = std::make_shared<int>(1);
+  std::weak_ptr<int> wp(p);
+
+  folly::CoreCachedSharedPtr<int> cached(p);
+  folly::CoreCachedWeakPtr<int> wcached(cached);
+
+  std::shared_ptr<int> p2 = cached.get();
+  std::weak_ptr<int> wp2 = wcached.get();
+  ASSERT_TRUE(p2 != nullptr);
+  ASSERT_EQ(*p2, 1);
+  ASSERT_FALSE(wp2.expired());
+
+  p.reset();
+  cached.reset();
+  // p2 should survive.
+  ASSERT_FALSE(wp.expired());
+  // Here we don't know anything about wp2: could be expired even if
+  // there is a living reference to the main object.
+
+  p2.reset();
+  ASSERT_TRUE(wp.expired());
+  ASSERT_TRUE(wp2.expired());
+}
+
+namespace {
+
+template <class Operation>
+void parallelRun(Operation op, size_t numThreads, size_t iters) {
+  std::vector<std::thread> threads;
+
+  // Prevent the compiler from hoisting code out of the loop.
+  auto opNoinline = [&]() FOLLY_NOINLINE { op(); };
+
+  for (size_t t = 0; t < numThreads; ++t) {
+    threads.emplace_back([&] {
+      for (size_t i = 0; i < iters; ++i) {
+        opNoinline();
+      }
+    });
+  }
+
+  for (auto& t : threads)
+    t.join();
+}
+
+void benchmarkSharedPtrCopy(size_t numThreads, size_t iters) {
+  auto p = std::make_shared<int>(1);
+  parallelRun([&] { return p; }, numThreads, iters);
+}
+
+void benchmarkWeakPtrLock(size_t numThreads, size_t iters) {
+  auto p = std::make_shared<int>(1);
+  std::weak_ptr<int> wp = p;
+  parallelRun([&] { return wp.lock(); }, numThreads, iters);
+}
+
+void benchmarkCoreCachedSharedPtrGet(size_t numThreads, size_t iters) {
+  folly::CoreCachedSharedPtr<int> p(std::make_shared<int>(1));
+  parallelRun([&] { return p.get(); }, numThreads, iters);
+}
+
+void benchmarkCoreCachedWeakPtrLock(size_t numThreads, size_t iters) {
+  folly::CoreCachedSharedPtr<int> p(std::make_shared<int>(1));
+  folly::CoreCachedWeakPtr<int> wp(p);
+  parallelRun([&] { return wp.get().lock(); }, numThreads, iters);
+}
+
+} // namespace
+
+BENCHMARK(SharedPtrSingleThread, n) {
+  benchmarkSharedPtrCopy(1, n);
+}
+BENCHMARK(WeakPtrSingleThread, n) {
+  benchmarkWeakPtrLock(1, n);
+}
+BENCHMARK(CoreCachedSharedPtrSingleThread, n) {
+  benchmarkCoreCachedSharedPtrGet(1, n);
+}
+BENCHMARK(CoreCachedWeakPtrSingleThread, n) {
+  benchmarkCoreCachedWeakPtrLock(1, n);
+}
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK(SharedPtr4Threads, n) {
+  benchmarkSharedPtrCopy(4, n);
+}
+BENCHMARK(WeakPtr4Threads, n) {
+  benchmarkWeakPtrLock(4, n);
+}
+BENCHMARK(CoreCachedSharedPtr4Threads, n) {
+  benchmarkCoreCachedSharedPtrGet(4, n);
+}
+BENCHMARK(CoreCachedWeakPtr4Threads, n) {
+  benchmarkCoreCachedWeakPtrLock(4, n);
+}
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK(SharedPtr16Threads, n) {
+  benchmarkSharedPtrCopy(16, n);
+}
+BENCHMARK(WeakPtr16Threads, n) {
+  benchmarkWeakPtrLock(16, n);
+}
+BENCHMARK(CoreCachedSharedPtr16Threads, n) {
+  benchmarkCoreCachedSharedPtrGet(16, n);
+}
+BENCHMARK(CoreCachedWeakPtr16Threads, n) {
+  benchmarkCoreCachedWeakPtrLock(16, n);
+}
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+  auto ret = RUN_ALL_TESTS();
+  if (ret == 0 && FLAGS_benchmark) {
+    folly::runBenchmarks();
+  }
+
+  return ret;
+}
index b1139f24939b158474b12aa9629ca8768990e78b..a2e2dcc39737d83aef9730cd95391ff8715ddba0 100644 (file)
@@ -36,9 +36,11 @@ void benchmark(size_t n) {
   for (size_t t = 0; t < threadCount; ++t) {
     ts.emplace_back([&]() {
         WeakPtr<int> weakPtr(mainPtr);
+        // Prevent the compiler from hoisting code out of the loop.
+        auto op = [&]() FOLLY_NOINLINE { weakPtr.lock(); };
 
         for (size_t i = 0; i < n; ++i) {
-          weakPtr.lock();
+          op();
         }
       });
   }