Commit 249e3805 authored by Andrii Grynenko's avatar Andrii Grynenko Committed by JoelMarcey

get_fast/get_weak_fast API for folly::Singleton

Summary: This adds API which makes folly::Singleton as performant as Meyers/static-object singletons.

Test Plan:
unit test + benchmark

============================================================================
folly/experimental/test/SingletonTest.cpp       relative  time/iter  iters/s
============================================================================
NormalSingleton                                            333.32ps    3.00G
MeyersSingleton                                  100.00%   333.33ps    3.00G
FollySingletonSlow                                 0.35%    94.36ns   10.60M
FollySingletonFast                                99.43%   335.24ps    2.98G
FollySingletonFastWeak                             0.62%    53.74ns   18.61M
============================================================================

Reviewed By: alikhtarov@fb.com

Subscribers: trunkagent, folly-diffs@

FB internal diff: D1741961

Signature: t1:1741961:1418765462:d9806f1bf5275bfbe2c4c53a41b735bda93753fe
parent adc966e1
...@@ -63,7 +63,7 @@ void SingletonVault::destroyInstance(SingletonMap::iterator entry_it) { ...@@ -63,7 +63,7 @@ void SingletonVault::destroyInstance(SingletonMap::iterator entry_it) {
<< "is " << entry.instance.get() << " with use_count of " << "is " << entry.instance.get() << " with use_count of "
<< entry.instance.use_count(); << entry.instance.use_count();
} }
entry.state = SingletonEntryState::Dead; entry.state = detail::SingletonEntryState::Dead;
entry.instance.reset(); entry.instance.reset();
} }
......
...@@ -161,6 +161,10 @@ class TypeDescriptor { ...@@ -161,6 +161,10 @@ class TypeDescriptor {
return ret; return ret;
} }
std::string name_raw() const {
return name_;
}
friend class TypeDescriptorHasher; friend class TypeDescriptorHasher;
bool operator==(const TypeDescriptor& other) const { bool operator==(const TypeDescriptor& other) const {
...@@ -178,6 +182,52 @@ class TypeDescriptorHasher { ...@@ -178,6 +182,52 @@ class TypeDescriptorHasher {
return folly::hash::hash_combine(ti.ti_, ti.name_); return folly::hash::hash_combine(ti.ti_, ti.name_);
} }
}; };
enum class SingletonEntryState {
Dead,
Living,
};
// An actual instance of a singleton, tracking the instance itself,
// its state as described above, and the create and teardown
// functions.
struct SingletonEntry {
typedef std::function<void(void*)> TeardownFunc;
typedef std::function<void*(void)> CreateFunc;
SingletonEntry(CreateFunc c, TeardownFunc t) :
create(std::move(c)), teardown(std::move(t)) {}
// mutex protects the entire entry during construction/destruction
std::mutex mutex;
// State of the singleton entry. If state is Living, instance_ptr and
// instance_weak can be safely accessed w/o synchronization.
std::atomic<SingletonEntryState> state{SingletonEntryState::Dead};
// the thread creating the singleton (only valid while creating an object)
std::thread::id creating_thread;
// The singleton itself and related functions.
// holds a shared_ptr to singleton instance, set when state is changed from
// Dead to Living. Reset when state is changed from Living to Dead.
std::shared_ptr<void> instance;
// weak_ptr to the singleton instance, set when state is changed from Dead
// to Living. We never write to this object after initialization, so it is
// safe to read it from different threads w/o synchronization if we know
// that state is set to Living
std::weak_ptr<void> instance_weak;
void* instance_ptr = nullptr;
CreateFunc create = nullptr;
TeardownFunc teardown = nullptr;
SingletonEntry(const SingletonEntry&) = delete;
SingletonEntry& operator=(const SingletonEntry&) = delete;
SingletonEntry& operator=(SingletonEntry&&) = delete;
SingletonEntry(SingletonEntry&&) = delete;
};
} }
class SingletonVault { class SingletonVault {
...@@ -196,7 +246,7 @@ class SingletonVault { ...@@ -196,7 +246,7 @@ class SingletonVault {
// registration is not complete. If validations succeeds, // registration is not complete. If validations succeeds,
// register a singleton of a given type with the create and teardown // register a singleton of a given type with the create and teardown
// functions. // functions.
void registerSingleton(detail::TypeDescriptor type, detail::SingletonEntry& registerSingleton(detail::TypeDescriptor type,
CreateFunc create, CreateFunc create,
TeardownFunc teardown) { TeardownFunc teardown) {
RWSpinLock::ReadHolder rh(&stateMutex_); RWSpinLock::ReadHolder rh(&stateMutex_);
...@@ -211,19 +261,21 @@ class SingletonVault { ...@@ -211,19 +261,21 @@ class SingletonVault {
RWSpinLock::ReadHolder rhMutex(&mutex_); RWSpinLock::ReadHolder rhMutex(&mutex_);
CHECK_THROW(singletons_.find(type) == singletons_.end(), std::logic_error); CHECK_THROW(singletons_.find(type) == singletons_.end(), std::logic_error);
registerSingletonImpl(type, create, teardown); return registerSingletonImpl(type, create, teardown);
} }
// Register a singleton of a given type with the create and teardown // Register a singleton of a given type with the create and teardown
// functions. Must hold reader locks on stateMutex_ and mutex_ // functions. Must hold reader locks on stateMutex_ and mutex_
// when invoking this function. // when invoking this function.
void registerSingletonImpl(detail::TypeDescriptor type, detail::SingletonEntry& registerSingletonImpl(detail::TypeDescriptor type,
CreateFunc create, CreateFunc create,
TeardownFunc teardown) { TeardownFunc teardown) {
RWSpinLock::UpgradedHolder wh(&mutex_); RWSpinLock::UpgradedHolder wh(&mutex_);
singletons_[type] = folly::make_unique<SingletonEntry>(std::move(create), singletons_[type] =
folly::make_unique<detail::SingletonEntry>(std::move(create),
std::move(teardown)); std::move(teardown));
return *singletons_[type];
} }
/* Register a mock singleton used for testing of singletons which /* Register a mock singleton used for testing of singletons which
...@@ -279,7 +331,7 @@ class SingletonVault { ...@@ -279,7 +331,7 @@ class SingletonVault {
if (type_ == Type::Strict) { if (type_ == Type::Strict) {
for (const auto& id_singleton_entry: singletons_) { for (const auto& id_singleton_entry: singletons_) {
const auto& singleton_entry = *id_singleton_entry.second; const auto& singleton_entry = *id_singleton_entry.second;
if (singleton_entry.state != SingletonEntryState::Dead) { if (singleton_entry.state != detail::SingletonEntryState::Dead) {
throw std::runtime_error( throw std::runtime_error(
"Singleton created before registration was complete."); "Singleton created before registration was complete.");
} }
...@@ -327,7 +379,7 @@ class SingletonVault { ...@@ -327,7 +379,7 @@ class SingletonVault {
size_t ret = 0; size_t ret = 0;
for (const auto& p : singletons_) { for (const auto& p : singletons_) {
if (p.second->state == SingletonEntryState::Living) { if (p.second->state == detail::SingletonEntryState::Living) {
++ret; ++ret;
} }
} }
...@@ -348,10 +400,6 @@ class SingletonVault { ...@@ -348,10 +400,6 @@ class SingletonVault {
// Each singleton in the vault can be in two states: dead // Each singleton in the vault can be in two states: dead
// (registered but never created), living (CreateFunc returned an instance). // (registered but never created), living (CreateFunc returned an instance).
enum class SingletonEntryState {
Dead,
Living,
};
void stateCheck(SingletonVaultState expected, void stateCheck(SingletonVaultState expected,
const char* msg="Unexpected singleton state change") { const char* msg="Unexpected singleton state change") {
...@@ -360,43 +408,6 @@ class SingletonVault { ...@@ -360,43 +408,6 @@ class SingletonVault {
} }
} }
// An actual instance of a singleton, tracking the instance itself,
// its state as described above, and the create and teardown
// functions.
struct SingletonEntry {
SingletonEntry(CreateFunc c, TeardownFunc t) :
create(std::move(c)), teardown(std::move(t)) {}
// mutex protects the entire entry during construction/destruction
std::mutex mutex;
// State of the singleton entry. If state is Living, instance_ptr and
// instance_weak can be safely accessed w/o synchronization.
std::atomic<SingletonEntryState> state{SingletonEntryState::Dead};
// the thread creating the singleton (only valid while creating an object)
std::thread::id creating_thread;
// The singleton itself and related functions.
// holds a shared_ptr to singleton instance, set when state is changed from
// Dead to Living. Reset when state is changed from Living to Dead.
std::shared_ptr<void> instance;
// weak_ptr to the singleton instance, set when state is changed from Dead
// to Living. We never write to this object after initialization, so it is
// safe to read it from different threads w/o synchronization if we know
// that state is set to Living
std::weak_ptr<void> instance_weak;
void* instance_ptr = nullptr;
CreateFunc create = nullptr;
TeardownFunc teardown = nullptr;
SingletonEntry(const SingletonEntry&) = delete;
SingletonEntry& operator=(const SingletonEntry&) = delete;
SingletonEntry& operator=(SingletonEntry&&) = delete;
SingletonEntry(SingletonEntry&&) = delete;
};
// This method only matters if registrationComplete() is never called. // This method only matters if registrationComplete() is never called.
// Otherwise destroyInstances is scheduled to be executed atexit. // Otherwise destroyInstances is scheduled to be executed atexit.
// //
...@@ -410,7 +421,7 @@ class SingletonVault { ...@@ -410,7 +421,7 @@ class SingletonVault {
// any of the singletons managed by folly::Singleton was requested. // any of the singletons managed by folly::Singleton was requested.
static void scheduleDestroyInstances(); static void scheduleDestroyInstances();
SingletonEntry* get_entry(detail::TypeDescriptor type) { detail::SingletonEntry* get_entry(detail::TypeDescriptor type) {
RWSpinLock::ReadHolder rh(&mutex_); RWSpinLock::ReadHolder rh(&mutex_);
auto it = singletons_.find(type); auto it = singletons_.find(type);
...@@ -425,10 +436,10 @@ class SingletonVault { ...@@ -425,10 +436,10 @@ class SingletonVault {
// Get a pointer to the living SingletonEntry for the specified // Get a pointer to the living SingletonEntry for the specified
// type. The singleton is created as part of this function, if // type. The singleton is created as part of this function, if
// necessary. // necessary.
SingletonEntry* get_entry_create(detail::TypeDescriptor type) { detail::SingletonEntry* get_entry_create(detail::TypeDescriptor type) {
auto entry = get_entry(type); auto entry = get_entry(type);
if (LIKELY(entry->state == SingletonEntryState::Living)) { if (LIKELY(entry->state == detail::SingletonEntryState::Living)) {
return entry; return entry;
} }
...@@ -442,7 +453,7 @@ class SingletonVault { ...@@ -442,7 +453,7 @@ class SingletonVault {
std::lock_guard<std::mutex> entry_lock(entry->mutex); std::lock_guard<std::mutex> entry_lock(entry->mutex);
if (entry->state == SingletonEntryState::Living) { if (entry->state == detail::SingletonEntryState::Living) {
return entry; return entry;
} }
...@@ -470,7 +481,7 @@ class SingletonVault { ...@@ -470,7 +481,7 @@ class SingletonVault {
// This has to be the last step, because once state is Living other threads // This has to be the last step, because once state is Living other threads
// may access instance and instance_weak w/o synchronization. // may access instance and instance_weak w/o synchronization.
entry->state.store(SingletonEntryState::Living); entry->state.store(detail::SingletonEntryState::Living);
{ {
RWSpinLock::WriteHolder wh(&mutex_); RWSpinLock::WriteHolder wh(&mutex_);
...@@ -479,7 +490,7 @@ class SingletonVault { ...@@ -479,7 +490,7 @@ class SingletonVault {
return entry; return entry;
} }
typedef std::unique_ptr<SingletonEntry> SingletonEntryPtr; typedef std::unique_ptr<detail::SingletonEntry> SingletonEntryPtr;
typedef std::unordered_map<detail::TypeDescriptor, typedef std::unordered_map<detail::TypeDescriptor,
SingletonEntryPtr, SingletonEntryPtr,
detail::TypeDescriptorHasher> SingletonMap; detail::TypeDescriptorHasher> SingletonMap;
...@@ -522,6 +533,14 @@ class Singleton { ...@@ -522,6 +533,14 @@ class Singleton {
return get_ptr({typeid(T), name}, vault); return get_ptr({typeid(T), name}, vault);
} }
T* get_fast() {
if (LIKELY(entry_->state == detail::SingletonEntryState::Living)) {
return reinterpret_cast<T*>(entry_->instance_ptr);
} else {
return get(type_descriptor_.name_raw().c_str(), vault_);
}
}
// If, however, you do need to hold a reference to the specific // If, however, you do need to hold a reference to the specific
// singleton, you can try to do so with a weak_ptr. Avoid this when // singleton, you can try to do so with a weak_ptr. Avoid this when
// possible but the inability to lock the weak pointer can be a // possible but the inability to lock the weak pointer can be a
...@@ -545,6 +564,20 @@ class Singleton { ...@@ -545,6 +564,20 @@ class Singleton {
return std::static_pointer_cast<T>(shared_void_ptr); return std::static_pointer_cast<T>(shared_void_ptr);
} }
std::weak_ptr<T> get_weak_fast() {
if (LIKELY(entry_->state == detail::SingletonEntryState::Living)) {
// This is ugly and inefficient, but there's no other way to do it,
// because there's no static_pointer_cast for weak_ptr.
auto shared_void_ptr = entry_->instance_weak.lock();
if (!shared_void_ptr) {
return std::weak_ptr<T>();
}
return std::static_pointer_cast<T>(shared_void_ptr);
} else {
return get_weak(type_descriptor_.name_raw().c_str(), vault_);
}
}
// Allow the Singleton<t> instance to also retrieve the underlying // Allow the Singleton<t> instance to also retrieve the underlying
// singleton, if desired. // singleton, if desired.
T* ptr() { return get_ptr(type_descriptor_, vault_); } T* ptr() { return get_ptr(type_descriptor_, vault_); }
...@@ -629,7 +662,7 @@ class Singleton { ...@@ -629,7 +662,7 @@ class Singleton {
vault_ = vault; vault_ = vault;
if (registerSingleton) { if (registerSingleton) {
vault->registerSingleton(type, c, getTeardownFunc(t)); entry_ = &(vault->registerSingleton(type, c, getTeardownFunc(t)));
} }
} }
...@@ -679,6 +712,11 @@ private: ...@@ -679,6 +712,11 @@ private:
} }
detail::TypeDescriptor type_descriptor_; detail::TypeDescriptor type_descriptor_;
// This is pointing to SingletonEntry paired with this singleton object. This
// is never reset, so each SingletonEntry should never be destroyed.
// We rely on the fact that Singleton destructor won't reset this pointer, so
// it can be "safely" used even after static Singleton object is destroyed.
detail::SingletonEntry* entry_;
SingletonVault* vault_; SingletonVault* vault_;
}; };
......
...@@ -425,7 +425,7 @@ BENCHMARK_RELATIVE(MeyersSingleton, n) { ...@@ -425,7 +425,7 @@ BENCHMARK_RELATIVE(MeyersSingleton, n) {
} }
} }
BENCHMARK_RELATIVE(FollySingleton, n) { BENCHMARK_RELATIVE(FollySingletonSlow, n) {
SingletonVault benchmark_vault; SingletonVault benchmark_vault;
Singleton<BenchmarkSingleton> benchmark_singleton( Singleton<BenchmarkSingleton> benchmark_singleton(
nullptr, nullptr, &benchmark_vault); nullptr, nullptr, &benchmark_vault);
...@@ -436,6 +436,28 @@ BENCHMARK_RELATIVE(FollySingleton, n) { ...@@ -436,6 +436,28 @@ BENCHMARK_RELATIVE(FollySingleton, n) {
} }
} }
BENCHMARK_RELATIVE(FollySingletonFast, n) {
SingletonVault benchmark_vault;
Singleton<BenchmarkSingleton> benchmark_singleton(
nullptr, nullptr, &benchmark_vault);
benchmark_vault.registrationComplete();
for (size_t i = 0; i < n; ++i) {
doNotOptimizeAway(benchmark_singleton.get_fast());
}
}
BENCHMARK_RELATIVE(FollySingletonFastWeak, n) {
SingletonVault benchmark_vault;
Singleton<BenchmarkSingleton> benchmark_singleton(
nullptr, nullptr, &benchmark_vault);
benchmark_vault.registrationComplete();
for (size_t i = 0; i < n; ++i) {
benchmark_singleton.get_weak_fast();
}
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]); google::InitGoogleLogging(argv[0]);
......
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