From a35bb6608d4029ccdc9c7fc42113018b142f8ba7 Mon Sep 17 00:00:00 2001 From: Pavel Vasilyev Date: Thu, 15 Feb 2024 21:37:53 +0100 Subject: [PATCH] settings: nvs: Fix first write issue with cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: When the setting nvs cache is disabled and `settings_nvs_save` is called, the function reads all stored setting name entries from NVS until either finds the desired setting name entry or reaches the last stored setting name entry. With the settings nvs cache enabled, `settings_nvs_save` runs through the cached setting name entries first. If the cached entry matches with the desired one, it immediately writes the new setting value to NVS that corresponds to the cached setting name entry. However, if the setting name entry is not found in the cache (which is the case for a new entry), `settings_nvs_save` reads all stored setting name entries from NVS again. This means that even if the number of stored entries in the settings is less than the cache size, for each new setting entry to be stored `settings_nvs_save` will first run through the cache, then read all stored setting name entries from NVS and only then will pick the next free name id for this new setting name entry and will finally store the new setting entry. This makes the cache ineffiсient for every new entry to be stored even when the cache size is always able to keep all setting entries that will be stored in NVS. Use-case: In the Bluetooth mesh there is a Replay Protection List which keeps sequence numbers of all nodes it received messages from. The RPL is stored persistently in NVS. The setting name entry is the source address of the node and the setting value entry is the sequence number. The common use case is when RPL is quite big (for example, 255 entries). With the current settings nvs cache implementation, every time the node stores a new RPL entry in settings (which is the first received message from a particular source address), `settings_nvs_save` will always check the cache first, then also read all stored entries in NVS and only then will figure out that this is a new entry. With every new RPL entry to be stored this search time increases. This behavior results in much worse performance in comparison with when the corresponding entry was already stored. E.g. on nRF52840, with bare minimal mesh stack configuration, when the cache is bigger than number of stored entries or close to it, storing of 255 RPL entries takes ~25 seconds. The time of subsequent store of 255 RPL entires is ~2 seconds with the cache. Solution: This commit improves the behavior of the first write by bypassing the reading from NVS if the following conditions are met: 1. `settings_nvs_load` was called, 2. the cache was not overflowed (bigger than the number of stored entries). As long as these 2 conditiones are met, it is safe to skip reading from NVS, pick the next free name id and write the value immediately. Signed-off-by: Pavel Vasilyev --- .../settings/include/settings/settings_nvs.h | 2 ++ subsys/settings/src/settings_nvs.c | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/subsys/settings/include/settings/settings_nvs.h b/subsys/settings/include/settings/settings_nvs.h index ce34e192c2..6db576da63 100644 --- a/subsys/settings/include/settings/settings_nvs.h +++ b/subsys/settings/include/settings/settings_nvs.h @@ -45,6 +45,8 @@ struct settings_nvs { } cache[CONFIG_SETTINGS_NVS_NAME_CACHE_SIZE]; uint16_t cache_next; + uint16_t cache_total; + bool loaded; #endif }; diff --git a/subsys/settings/src/settings_nvs.c b/subsys/settings/src/settings_nvs.c index c6efa6cb28..6764c7587a 100644 --- a/subsys/settings/src/settings_nvs.c +++ b/subsys/settings/src/settings_nvs.c @@ -74,6 +74,8 @@ int settings_nvs_dst(struct settings_nvs *cf) } #if CONFIG_SETTINGS_NVS_NAME_CACHE +#define SETTINGS_NVS_CACHE_OVFL(cf) ((cf)->cache_total > ARRAY_SIZE((cf)->cache)) + static void settings_nvs_cache_add(struct settings_nvs *cf, const char *name, uint16_t name_id) { @@ -129,12 +131,22 @@ static int settings_nvs_load(struct settings_store *cs, ssize_t rc1, rc2; uint16_t name_id = NVS_NAMECNT_ID; +#if CONFIG_SETTINGS_NVS_NAME_CACHE + uint16_t cached = 0; + + cf->loaded = false; +#endif + name_id = cf->last_name_id + 1; while (1) { name_id--; if (name_id == NVS_NAMECNT_ID) { +#if CONFIG_SETTINGS_NVS_NAME_CACHE + cf->loaded = true; + cf->cache_total = cached; +#endif break; } @@ -186,6 +198,7 @@ static int settings_nvs_load(struct settings_store *cs, #if CONFIG_SETTINGS_NVS_NAME_CACHE settings_nvs_cache_add(cf, name, name_id); + cached++; #endif ret = settings_call_set_handler( @@ -231,6 +244,13 @@ static int settings_nvs_save(struct settings_store *cs, const char *name, write_name_id = cf->last_name_id + 1; write_name = true; +#if CONFIG_SETTINGS_NVS_NAME_CACHE + /* We can skip reading NVS if we know that the cache wasn't overflowed. */ + if (cf->loaded && !SETTINGS_NVS_CACHE_OVFL(cf)) { + goto found; + } +#endif + while (1) { name_id--; if (name_id == NVS_NAMECNT_ID) { @@ -325,6 +345,9 @@ found: #if CONFIG_SETTINGS_NVS_NAME_CACHE if (!name_in_cache) { settings_nvs_cache_add(cf, name, write_name_id); + if (cf->loaded && !SETTINGS_NVS_CACHE_OVFL(cf)) { + cf->cache_total++; + } } #endif