From d8744e6db8f97e4d721d098010d1b1115e31403f Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 5 Dec 2020 18:24:41 +0100 Subject: [PATCH] Add caching to Config::Info The goal of this change is to make Config::Get(const Info&) fast so that we can use it in hot paths. --- Source/Android/jni/NativeConfig.cpp | 2 +- Source/Core/Common/Config/Config.cpp | 21 +++++--- Source/Core/Common/Config/Config.h | 26 +++++++-- Source/Core/Common/Config/ConfigInfo.h | 75 ++++++++++++++++++++++++-- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/Source/Android/jni/NativeConfig.cpp b/Source/Android/jni/NativeConfig.cpp index ef4c847ee2..9de9a16358 100644 --- a/Source/Android/jni/NativeConfig.cpp +++ b/Source/Android/jni/NativeConfig.cpp @@ -86,7 +86,7 @@ template static void Set(jint layer, const Config::Location& location, T value) { GetLayer(layer, location)->Set(location, value); - Config::InvokeConfigChangedCallbacks(); + Config::OnConfigChanged(); } #ifdef __cplusplus diff --git a/Source/Core/Common/Config/Config.cpp b/Source/Core/Common/Config/Config.cpp index 03c0aed72e..071d009b51 100644 --- a/Source/Core/Common/Config/Config.cpp +++ b/Source/Core/Common/Config/Config.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include @@ -17,6 +18,7 @@ using Layers = std::map>; static Layers s_layers; static std::list s_callbacks; static u32 s_callback_guards = 0; +static std::atomic s_config_version = 0; static std::shared_mutex s_layers_rw_lock; @@ -31,7 +33,7 @@ static void AddLayerInternal(std::shared_ptr layer) const Config::LayerType layer_type = layer->GetLayer(); s_layers.insert_or_assign(layer_type, std::move(layer)); } - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } void AddLayer(std::unique_ptr loader) @@ -59,7 +61,7 @@ void RemoveLayer(LayerType layer) s_layers.erase(layer); } - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } void AddConfigChangedCallback(ConfigChangedCallback func) @@ -67,15 +69,22 @@ void AddConfigChangedCallback(ConfigChangedCallback func) s_callbacks.emplace_back(std::move(func)); } -void InvokeConfigChangedCallbacks() +void OnConfigChanged() { if (s_callback_guards) return; + s_config_version.fetch_add(1, std::memory_order_relaxed); + for (const auto& callback : s_callbacks) callback(); } +u64 GetConfigVersion() +{ + return s_config_version.load(std::memory_order_relaxed); +} + // Explicit load and save of layers void Load() { @@ -85,7 +94,7 @@ void Load() for (auto& layer : s_layers) layer.second->Load(); } - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } void Save() @@ -96,7 +105,7 @@ void Save() for (auto& layer : s_layers) layer.second->Save(); } - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } void Init() @@ -207,7 +216,7 @@ ConfigChangeCallbackGuard::~ConfigChangeCallbackGuard() if (--s_callback_guards) return; - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } } // namespace Config diff --git a/Source/Core/Common/Config/Config.h b/Source/Core/Common/Config/Config.h index 21662d4167..e0d110719e 100644 --- a/Source/Core/Common/Config/Config.h +++ b/Source/Core/Common/Config/Config.h @@ -24,7 +24,10 @@ std::shared_ptr GetLayer(LayerType layer); void RemoveLayer(LayerType layer); void AddConfigChangedCallback(ConfigChangedCallback func); -void InvokeConfigChangedCallbacks(); +void OnConfigChanged(); + +// Returns the number of times the config has changed in the current execution of the program +u64 GetConfigVersion(); // Explicit load and save of layers void Load(); @@ -51,6 +54,23 @@ T Get(LayerType layer, const Info& info) template T Get(const Info& info) +{ + CachedValue cached = info.GetCachedValue(); + const u64 config_version = GetConfigVersion(); + + if (cached.config_version < config_version) + { + cached.value = GetUncached(info); + cached.config_version = config_version; + + info.SetCachedValue(cached); + } + + return cached.value; +} + +template +T GetUncached(const Info& info) { const std::optional str = GetAsString(info.GetLocation()); if (!str) @@ -75,7 +95,7 @@ template void Set(LayerType layer, const Info& info, const std::common_type_t& value) { GetLayer(layer)->Set(info, value); - InvokeConfigChangedCallbacks(); + OnConfigChanged(); } template @@ -99,7 +119,7 @@ void SetBaseOrCurrent(const Info& info, const std::common_type_t& value) Set(LayerType::CurrentRun, info, value); } -// Used to defer InvokeConfigChangedCallbacks until after the completion of many config changes. +// Used to defer OnConfigChanged until after the completion of many config changes. class ConfigChangeCallbackGuard { public: diff --git a/Source/Core/Common/Config/ConfigInfo.h b/Source/Core/Common/Config/ConfigInfo.h index 56d3a43a31..0b9b0ad653 100644 --- a/Source/Core/Common/Config/ConfigInfo.h +++ b/Source/Core/Common/Config/ConfigInfo.h @@ -4,9 +4,13 @@ #pragma once +#include +#include #include #include +#include +#include "Common/CommonTypes.h" #include "Common/Config/Enums.h" namespace Config @@ -29,30 +33,93 @@ struct Location bool operator<(const Location& other) const; }; +template +struct CachedValue +{ + T value; + u64 config_version; +}; + template class Info { public: constexpr Info(const Location& location, const T& default_value) - : m_location{location}, m_default_value{default_value} + : m_location{location}, m_default_value{default_value}, m_cached_value{default_value, 0} { } + Info(const Info& other) { *this = other; } + + // Not thread-safe + Info(Info&& other) { *this = std::move(other); } + + // Make it easy to convert Info into Info> + // so that enum settings can still easily work with code that doesn't care about the enum values. + template >::value>* = nullptr> + Info(const Info& other) + { + *this = other; + } + + Info& operator=(const Info& other) + { + m_location = other.GetLocation(); + m_default_value = other.GetDefaultValue(); + m_cached_value = other.GetCachedValue(); + return *this; + } + + // Not thread-safe + Info& operator=(Info&& other) + { + m_location = std::move(other.m_location); + m_default_value = std::move(other.m_default_value); + m_cached_value = std::move(other.m_cached_value); + return *this; + } + // Make it easy to convert Info into Info> // so that enum settings can still easily work with code that doesn't care about the enum values. template >::value>* = nullptr> - constexpr Info(const Info& other) - : m_location{other.GetLocation()}, m_default_value{static_cast>( - other.GetDefaultValue())} + Info& operator=(const Info& other) { + m_location = other.GetLocation(); + m_default_value = static_cast(other.GetDefaultValue()); + m_cached_value = other.template GetCachedValueCasted(); + return *this; } constexpr const Location& GetLocation() const { return m_location; } constexpr const T& GetDefaultValue() const { return m_default_value; } + CachedValue GetCachedValue() const + { + std::shared_lock lock(m_cached_value_mutex); + return m_cached_value; + } + + template + CachedValue GetCachedValueCasted() const + { + std::shared_lock lock(m_cached_value_mutex); + return CachedValue{static_cast(m_cached_value.value), m_cached_value.config_version}; + } + + void SetCachedValue(const CachedValue& cached_value) const + { + std::unique_lock lock(m_cached_value_mutex); + if (m_cached_value.config_version < cached_value.config_version) + m_cached_value = cached_value; + } + private: Location m_location; T m_default_value; + + mutable CachedValue m_cached_value; + mutable std::shared_mutex m_cached_value_mutex; }; } // namespace Config