Commit 937fc980 authored by Giuseppe Ottaviano's avatar Giuseppe Ottaviano Committed by Facebook GitHub Bot

Use CoreCachedSharedPtr in Singleton

Summary: `CoreCachedSharedPtr` is almost as fast as `ReadMostlySharedPtr`, so we can use it to have a better default that does not have pathological behavior under heavy contention. `try_get_fast()` is still useful if we need to squeeze out the last cycle.

Reviewed By: philippv, luciang

Differential Revision: D29812053

fbshipit-source-id: 49e9e53444f8099dbfe13e36c3c07c1b57bb89fb
parent 4baba282
...@@ -117,7 +117,7 @@ std::weak_ptr<T> SingletonHolder<T>::get_weak() { ...@@ -117,7 +117,7 @@ std::weak_ptr<T> SingletonHolder<T>::get_weak() {
createInstance(); createInstance();
} }
return instance_weak_; return instance_weak_core_cached_.get();
} }
template <typename T> template <typename T>
...@@ -128,7 +128,7 @@ std::shared_ptr<T> SingletonHolder<T>::try_get() { ...@@ -128,7 +128,7 @@ std::shared_ptr<T> SingletonHolder<T>::try_get() {
createInstance(); createInstance();
} }
return instance_weak_.lock(); return instance_weak_core_cached_.lock();
} }
template <typename T> template <typename T>
...@@ -182,6 +182,7 @@ void SingletonHolder<T>::destroyInstance() { ...@@ -182,6 +182,7 @@ void SingletonHolder<T>::destroyInstance() {
} }
} }
state_ = SingletonHolderState::Dead; state_ = SingletonHolderState::Dead;
instance_core_cached_.reset();
instance_.reset(); instance_.reset();
instance_copy_.reset(); instance_copy_.reset();
if (destroy_baton_) { if (destroy_baton_) {
...@@ -310,8 +311,10 @@ void SingletonHolder<T>::createInstance() { ...@@ -310,8 +311,10 @@ void SingletonHolder<T>::createInstance() {
instance_weak_ = instance; instance_weak_ = instance;
instance_ptr_ = instance.get(); instance_ptr_ = instance.get();
instance_core_cached_.reset(instance);
instance_.reset(std::move(instance)); instance_.reset(std::move(instance));
instance_weak_fast_ = instance_; instance_weak_fast_ = instance_;
instance_weak_core_cached_.reset(instance_core_cached_);
destroy_baton_ = std::move(destroy_baton); destroy_baton_ = std::move(destroy_baton);
print_destructor_stack_trace_ = std::move(print_destructor_stack_trace); print_destructor_stack_trace_ = std::move(print_destructor_stack_trace);
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
#include <folly/Executor.h> #include <folly/Executor.h>
#include <folly/Memory.h> #include <folly/Memory.h>
#include <folly/Synchronized.h> #include <folly/Synchronized.h>
#include <folly/concurrency/CoreCachedSharedPtr.h>
#include <folly/detail/Singleton.h> #include <folly/detail/Singleton.h>
#include <folly/detail/StaticSingletonManager.h> #include <folly/detail/StaticSingletonManager.h>
#include <folly/experimental/ReadMostlySharedPtr.h> #include <folly/experimental/ReadMostlySharedPtr.h>
...@@ -357,13 +358,18 @@ struct SingletonHolder : public SingletonHolderBase { ...@@ -357,13 +358,18 @@ struct SingletonHolder : public SingletonHolderBase {
folly::ReadMostlyMainPtr<T> instance_; folly::ReadMostlyMainPtr<T> instance_;
// used to release all ReadMostlyMainPtrs at once // used to release all ReadMostlyMainPtrs at once
folly::ReadMostlySharedPtr<T> instance_copy_; folly::ReadMostlySharedPtr<T> instance_copy_;
// weak_ptr to the singleton instance, set when state is changed from Dead // per-core shared_ptrs that in turn hold the instance shared_ptr, to avoid
// to Living. We never write to this object after initialization, so it is // contention in acquiring them.
// safe to read it from different threads w/o synchronization if we know folly::CoreCachedSharedPtr<T> instance_core_cached_;
// that state is set to Living // weak references to the previous pointers. These are never written to after
// initialization, so they're safe to read without synchronization once the
// state has transitioned to Living.
// instance_weak_ is a reference to the main instance, so it is authoritative
// on whether the instance is expired.
std::weak_ptr<T> instance_weak_; std::weak_ptr<T> instance_weak_;
// Fast equivalent of instance_weak_
folly::ReadMostlyWeakPtr<T> instance_weak_fast_; folly::ReadMostlyWeakPtr<T> instance_weak_fast_;
folly::CoreCachedWeakPtr<T> instance_weak_core_cached_;
// Time we wait on destroy_baton after releasing Singleton shared_ptr. // Time we wait on destroy_baton after releasing Singleton shared_ptr.
std::shared_ptr<folly::Baton<>> destroy_baton_; std::shared_ptr<folly::Baton<>> destroy_baton_;
T* instance_ptr_ = nullptr; T* instance_ptr_ = nullptr;
......
...@@ -19,13 +19,16 @@ ...@@ -19,13 +19,16 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <folly/Portability.h>
#include <folly/concurrency/CacheLocality.h> #include <folly/concurrency/CacheLocality.h>
#include <folly/container/Enumerate.h> #include <folly/container/Enumerate.h>
#include <folly/synchronization/Hazptr.h> #include <folly/synchronization/Hazptr.h>
namespace folly { namespace folly {
constexpr size_t kCoreCachedSharedPtrDefaultNumSlots = 64; // On mobile we do not expect high concurrency, and memory is more important, so
// use more conservative caching.
constexpr size_t kCoreCachedSharedPtrDefaultNumSlots = kIsMobile ? 4 : 64;
/** /**
* This class creates core-local caches for a given shared_ptr, to * This class creates core-local caches for a given shared_ptr, to
...@@ -40,9 +43,8 @@ constexpr size_t kCoreCachedSharedPtrDefaultNumSlots = 64; ...@@ -40,9 +43,8 @@ constexpr size_t kCoreCachedSharedPtrDefaultNumSlots = 64;
template <class T, size_t kNumSlots = kCoreCachedSharedPtrDefaultNumSlots> template <class T, size_t kNumSlots = kCoreCachedSharedPtrDefaultNumSlots>
class CoreCachedSharedPtr { class CoreCachedSharedPtr {
public: public:
explicit CoreCachedSharedPtr(const std::shared_ptr<T>& p = nullptr) { CoreCachedSharedPtr() = default;
reset(p); explicit CoreCachedSharedPtr(const std::shared_ptr<T>& p) { reset(p); }
}
void reset(const std::shared_ptr<T>& p = nullptr) { void reset(const std::shared_ptr<T>& p = nullptr) {
// Allocate each Holder in a different CoreRawAllocator stripe to // Allocate each Holder in a different CoreRawAllocator stripe to
...@@ -71,7 +73,13 @@ class CoreCachedSharedPtr { ...@@ -71,7 +73,13 @@ class CoreCachedSharedPtr {
template <class T, size_t kNumSlots = kCoreCachedSharedPtrDefaultNumSlots> template <class T, size_t kNumSlots = kCoreCachedSharedPtrDefaultNumSlots>
class CoreCachedWeakPtr { class CoreCachedWeakPtr {
public: public:
CoreCachedWeakPtr() = default;
explicit CoreCachedWeakPtr(const CoreCachedSharedPtr<T, kNumSlots>& p) { explicit CoreCachedWeakPtr(const CoreCachedSharedPtr<T, kNumSlots>& p) {
reset(p);
}
void reset() { *this = {}; }
void reset(const CoreCachedSharedPtr<T, kNumSlots>& p) {
for (auto slot : folly::enumerate(slots_)) { for (auto slot : folly::enumerate(slots_)) {
*slot = p.slots_[slot.index]; *slot = p.slots_[slot.index];
} }
...@@ -81,6 +89,7 @@ class CoreCachedWeakPtr { ...@@ -81,6 +89,7 @@ class CoreCachedWeakPtr {
return slots_[AccessSpreader<>::cachedCurrent(kNumSlots)]; return slots_[AccessSpreader<>::cachedCurrent(kNumSlots)];
} }
// Faster than get().lock(), as it avoid one weak count cycle.
std::shared_ptr<T> lock() const { std::shared_ptr<T> lock() const {
return slots_[AccessSpreader<>::cachedCurrent(kNumSlots)].lock(); return slots_[AccessSpreader<>::cachedCurrent(kNumSlots)].lock();
} }
......
...@@ -315,7 +315,6 @@ TEST(Singleton, SharedPtrUsage) { ...@@ -315,7 +315,6 @@ TEST(Singleton, SharedPtrUsage) {
auto shared_s1 = weak_s1.lock(); auto shared_s1 = weak_s1.lock();
EXPECT_EQ(shared_s1.get(), s1); EXPECT_EQ(shared_s1.get(), s1);
EXPECT_EQ(shared_s1.use_count(), 2);
auto old_serial = shared_s1->serial_number; auto old_serial = shared_s1->serial_number;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment