Skip to content

Commit c23c21f

Browse files
committed
refactor(nvs_flash): Improved Blob performance
The findItem method was improved to use a hash list in RAM when searching for BLOB data chunks The findItem method was extended with a parameter that returns the position of an item on the page, if it is found The algorithm for matching existing variable-length data (such as strings and BLOBs) with new values was enhanced by comparing the CRC32 of the data chunks before reading the data from flash
1 parent c3239ce commit c23c21f

File tree

6 files changed

+206
-124
lines changed

6 files changed

+206
-124
lines changed

components/nvs_flash/src/nvs_page.cpp

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -250,33 +250,19 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c
250250
return ESP_OK;
251251
}
252252

253-
esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart)
253+
// Reads the data entries of the variable length item.
254+
// The metadata entry is already read in the item object.
255+
// index is the index of the metadata entry on the page.
256+
// data is pointer to the buffer where the data will be copied to. It has to be at least
257+
// item.varLength.dataSize bytes long.
258+
// The function returns ESP_OK if the data was read successfully, or an error code if there was an error.
259+
esp_err_t Page::readVariableLengthItemData(const Item& item, const size_t index, void* data)
254260
{
255-
size_t index = 0;
256-
Item item;
257-
258261
if (mState == PageState::INVALID) {
259262
return ESP_ERR_NVS_INVALID_STATE;
260263
}
261264

262-
esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart);
263-
if (rc != ESP_OK) {
264-
return rc;
265-
}
266-
267-
if (!isVariableLengthType(datatype)) {
268-
if (dataSize != getAlignmentForType(datatype)) {
269-
return ESP_ERR_NVS_TYPE_MISMATCH;
270-
}
271-
272-
memcpy(data, item.data, dataSize);
273-
return ESP_OK;
274-
}
275-
276-
if (dataSize < static_cast<size_t>(item.varLength.dataSize)) {
277-
return ESP_ERR_NVS_INVALID_LENGTH;
278-
}
279-
265+
esp_err_t rc;
280266
uint8_t* dst = reinterpret_cast<uint8_t*>(data);
281267
size_t left = item.varLength.dataSize;
282268
for (size_t i = index + 1; i < index + item.span; ++i) {
@@ -301,6 +287,36 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo
301287
return ESP_OK;
302288
}
303289

290+
esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart)
291+
{
292+
size_t index = 0;
293+
Item item;
294+
295+
if (mState == PageState::INVALID) {
296+
return ESP_ERR_NVS_INVALID_STATE;
297+
}
298+
299+
esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart);
300+
if (rc != ESP_OK) {
301+
return rc;
302+
}
303+
304+
if (!isVariableLengthType(datatype)) {
305+
if (dataSize != getAlignmentForType(datatype)) {
306+
return ESP_ERR_NVS_TYPE_MISMATCH;
307+
}
308+
309+
memcpy(data, item.data, dataSize);
310+
return ESP_OK;
311+
}
312+
313+
if (dataSize < static_cast<size_t>(item.varLength.dataSize)) {
314+
return ESP_ERR_NVS_INVALID_LENGTH;
315+
}
316+
317+
return readVariableLengthItemData(item, index, data);
318+
}
319+
304320
esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart)
305321
{
306322
size_t index = 0;
@@ -330,9 +346,23 @@ esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, con
330346
return ESP_ERR_NVS_INVALID_LENGTH;
331347
}
332348

349+
// We have metadata of the variable length data chunk. It contains the length of the data and the crc32.
350+
// As a first step we can calculate the crc32 of the data buffer to be compared with the crc32 of the item in the flash.
351+
// If they are not equal, immediately return ESP_ERR_NVS_CONTENT_DIFFERS.
352+
// If they are equal, to avoid crc32 collision false positive, we will read the data from the flash entry by entry and compare
353+
// it with the respective chunk of input data buffer. The crc32 of the data read from the flash will be calculated on the fly.
354+
// At the end, we will compare the crc32 of the data read from the flash with the crc32 of the metadata item in the flash to make sure
355+
// that the data in the flash is not corrupted.
356+
if (Item::calculateCrc32(reinterpret_cast<const uint8_t * >(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
357+
return ESP_ERR_NVS_CONTENT_DIFFERS;
358+
}
359+
333360
const uint8_t* dst = reinterpret_cast<const uint8_t*>(data);
334361
size_t left = item.varLength.dataSize;
335-
for (size_t i = index + 1; i < index + item.span; ++i) {
362+
uint32_t accumulatedCRC32;
363+
size_t initial_index = index + 1;
364+
365+
for (size_t i = initial_index; i < index + item.span; ++i) {
336366
Item ditem;
337367
rc = readEntry(i, ditem);
338368
if (rc != ESP_OK) {
@@ -343,11 +373,18 @@ esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, con
343373
if (memcmp(dst, ditem.rawData, willCopy)) {
344374
return ESP_ERR_NVS_CONTENT_DIFFERS;
345375
}
376+
377+
// Calculate the crc32 of the actual ditem.rawData buffer. Do not pass accumulatedCRC32 in the first call.
378+
// In the first call, calculateCrc32 will use its default. In the subsequent calls, accumulatedCRC32 is the crc32 of the previous buffer.
379+
accumulatedCRC32 = Item::calculateCrc32(ditem.rawData, willCopy, (i == initial_index) ? nullptr : &accumulatedCRC32);
380+
346381
left -= willCopy;
347382
dst += willCopy;
348383
}
349-
if (Item::calculateCrc32(reinterpret_cast<const uint8_t * >(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
350-
return ESP_ERR_NVS_NOT_FOUND;
384+
// Check if the CRC32 calculated on the fly matches the variable length data CRC32 indicated in the metadata entry.
385+
// If they are not equal, it means the data in the flash is corrupt, we will return ESP_ERR_NVS_CONTENT_DIFFERS.
386+
if (accumulatedCRC32 != item.varLength.dataCrc32) {
387+
return ESP_ERR_NVS_CONTENT_DIFFERS;
351388
}
352389

353390
return ESP_OK;

components/nvs_flash/src/nvs_page.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ class Page : public intrusive_list_node<Page>, public ExceptionlessAllocatable
8888

8989
esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY);
9090

91+
esp_err_t readVariableLengthItemData(const Item& item, const size_t index, void* data);
92+
9193
esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
9294

9395
esp_err_t cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);

0 commit comments

Comments
 (0)