Commit ecdcc6d8 authored by Anton Likhtarov's avatar Anton Likhtarov Committed by Facebook Github Bot

Perf: specialization for small types

Summary:
- Change the get() API to return T by value if T is small
- Internally store small T in a global atomic to avoid thread local lookups

Differential Revision: D9982433

fbshipit-source-id: c42d06a75b9ff48307a90f77d0bb72bfa6375d6c
parent 3a2d6a36
...@@ -66,17 +66,19 @@ template <class T> ...@@ -66,17 +66,19 @@ template <class T>
class Setting { class Setting {
public: public:
/** /**
* Returns the setting's current value. Note that the returned * Returns the setting's current value.
* reference is not guaranteed to be long-lived and should not be *
* saved anywhere. In particular, a set() call might invalidate a * As an optimization, returns by value for small types, and by
* reference obtained here after some amount of time (on the order * const& for larger types. Note that the returned reference is not
* of minutes). * guaranteed to be long-lived and should not be saved anywhere. In
* particular, a set() call might invalidate a reference obtained
* here after some amount of time (on the order of minutes).
*/ */
const T& operator*() const { std::conditional_t<IsSmallPOD<T>::value, T, const T&> operator*() const {
return core_.get(); return core_.get();
} }
const T* operator->() const { const T* operator->() const {
return &core_.get(); return &core_.getSlow();
} }
/** /**
...@@ -93,8 +95,12 @@ class Setting { ...@@ -93,8 +95,12 @@ class Setting {
core_.set(t, reason); core_.set(t, reason);
} }
Setting(SettingMetadata meta, T defaultValue) Setting(
: meta_(std::move(meta)), core_(meta_, std::move(defaultValue)) {} SettingMetadata meta,
T defaultValue,
std::atomic<uint64_t>& trivialStorage)
: meta_(std::move(meta)),
core_(meta_, std::move(defaultValue), trivialStorage) {}
private: private:
SettingMetadata meta_; SettingMetadata meta_;
...@@ -163,26 +169,32 @@ using TypeIdentityT = typename TypeIdentity<T>::type; ...@@ -163,26 +169,32 @@ using TypeIdentityT = typename TypeIdentity<T>::type;
* @param _def default value for the setting * @param _def default value for the setting
* @param _desc setting documentation * @param _desc setting documentation
*/ */
#define FOLLY_SETTING_DEFINE(_project, _name, _Type, _def, _desc) \ #define FOLLY_SETTING_DEFINE(_project, _name, _Type, _def, _desc) \
/* Fastpath optimization, see notes in FOLLY_SETTINGS_DEFINE_LOCAL_FUNC__. \ /* Fastpath optimization, see notes in FOLLY_SETTINGS_DEFINE_LOCAL_FUNC__. \
Aggregate all off these together in a single section for better TLB \ Aggregate all off these together in a single section for better TLB \
and cache locality. */ \ and cache locality. */ \
__attribute__((__section__(".folly.settings.cache"))) \ __attribute__((__section__(".folly.settings.cache"))) \
std::atomic<folly::settings::detail::Setting<_Type>*> \ std::atomic<folly::settings::detail::Setting<_Type>*> \
FOLLY_SETTINGS_CACHE__##_project##_##_name; \ FOLLY_SETTINGS_CACHE__##_project##_##_name; \
/* Meyers singleton to avoid SIOF */ \ /* Location for the small value cache (if _Type is small and trivial). \
FOLLY_NOINLINE folly::settings::detail::Setting<_Type>& \ Intentionally located right after the pointer cache above to take \
FOLLY_SETTINGS_FUNC__##_project##_##_name() { \ advantage of the prefetching */ \
static folly::Indestructible<folly::settings::detail::Setting<_Type>> \ __attribute__((__section__(".folly.settings.cache"))) std::atomic<uint64_t> \
setting( \ FOLLY_SETTINGS_TRIVIAL__##_project##_##_name; \
folly::settings::SettingMetadata{ \ /* Meyers singleton to avoid SIOF */ \
#_project, #_name, #_Type, typeid(_Type), #_def, _desc}, \ FOLLY_NOINLINE folly::settings::detail::Setting<_Type>& \
folly::settings::detail::TypeIdentityT<_Type>{_def}); \ FOLLY_SETTINGS_FUNC__##_project##_##_name() { \
return *setting; \ static folly::Indestructible<folly::settings::detail::Setting<_Type>> \
} \ setting( \
/* Ensure the setting is registered even if not used in program */ \ folly::settings::SettingMetadata{ \
auto& FOLLY_SETTINGS_INIT__##_project##_##_name = \ #_project, #_name, #_Type, typeid(_Type), #_def, _desc}, \
FOLLY_SETTINGS_FUNC__##_project##_##_name(); \ folly::settings::detail::TypeIdentityT<_Type>{_def}, \
FOLLY_SETTINGS_TRIVIAL__##_project##_##_name); \
return *setting; \
} \
/* Ensure the setting is registered even if not used in program */ \
auto& FOLLY_SETTINGS_INIT__##_project##_##_name = \
FOLLY_SETTINGS_FUNC__##_project##_##_name(); \
FOLLY_SETTINGS_DEFINE_LOCAL_FUNC__(_project, _name, _Type, char) FOLLY_SETTINGS_DEFINE_LOCAL_FUNC__(_project, _name, _Type, char)
/** /**
......
...@@ -33,6 +33,15 @@ struct SettingMetadata; ...@@ -33,6 +33,15 @@ struct SettingMetadata;
namespace detail { namespace detail {
/**
* Can we store T in a global atomic?
*/
template <class T>
struct IsSmallPOD
: std::integral_constant<
bool,
std::is_trivial<T>::value && sizeof(T) <= sizeof(uint64_t)> {};
template <class T> template <class T>
struct SettingContents { struct SettingContents {
std::string updateReason; std::string updateReason;
...@@ -85,24 +94,54 @@ class SettingCore : public SettingCoreBase { ...@@ -85,24 +94,54 @@ class SettingCore : public SettingCoreBase {
return meta_; return meta_;
} }
const T& get() const { std::conditional_t<IsSmallPOD<T>::value, T, const T&> get() const {
return getImpl(IsSmallPOD<T>(), trivialStorage_);
}
const T& getSlow() const {
return getImpl(std::false_type{}, trivialStorage_);
}
/***
* SmallPOD version: just read the global atomic
*/
T getImpl(std::true_type, std::atomic<uint64_t>& trivialStorage) const {
uint64_t v = trivialStorage.load();
T t;
std::memcpy(&t, &v, sizeof(T));
return t;
}
/**
* Non-SmallPOD version: read the thread local shared_ptr
*/
const T& getImpl(std::false_type, std::atomic<uint64_t>& /* ignored */)
const {
return const_cast<SettingCore*>(this)->tlValue()->value; return const_cast<SettingCore*>(this)->tlValue()->value;
} }
void set(const T& t, StringPiece reason) { void set(const T& t, StringPiece reason) {
SharedMutex::WriteHolder lg(globalLock_); SharedMutex::WriteHolder lg(globalLock_);
globalValue_ = std::make_shared<Contents>(reason.str(), t); globalValue_ = std::make_shared<Contents>(reason.str(), t);
if (IsSmallPOD<T>::value) {
uint64_t v = 0;
std::memcpy(&v, &t, sizeof(T));
trivialStorage_.store(v);
}
++(*globalVersion_); ++(*globalVersion_);
} }
SettingCore(const SettingMetadata& meta, T defaultValue) SettingCore(
const SettingMetadata& meta,
T defaultValue,
std::atomic<uint64_t>& trivialStorage)
: meta_(meta), : meta_(meta),
defaultValue_(std::move(defaultValue)), defaultValue_(std::move(defaultValue)),
globalValue_(std::make_shared<Contents>("default", defaultValue_)), trivialStorage_(trivialStorage),
localValue_([]() { localValue_([]() {
return new CachelinePadded< return new CachelinePadded<
Indestructible<std::pair<size_t, std::shared_ptr<Contents>>>>( Indestructible<std::pair<size_t, std::shared_ptr<Contents>>>>(
0, nullptr); 0, nullptr);
}) { }) {
set(defaultValue_, "default");
registerSetting(*this); registerSetting(*this);
} }
...@@ -113,6 +152,8 @@ class SettingCore : public SettingCoreBase { ...@@ -113,6 +152,8 @@ class SettingCore : public SettingCoreBase {
SharedMutex globalLock_; SharedMutex globalLock_;
std::shared_ptr<Contents> globalValue_; std::shared_ptr<Contents> globalValue_;
std::atomic<uint64_t>& trivialStorage_;
/* Local versions start at 0, this will force a read on first local access. */ /* Local versions start at 0, this will force a read on first local access. */
CachelinePadded<std::atomic<size_t>> globalVersion_{1}; CachelinePadded<std::atomic<size_t>> globalVersion_{1};
......
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