feat(cache): implement dynamic array with second chance algorithm class (#8433)

This commit is contained in:
André Costa
2025-07-24 13:30:12 -03:00
committed by GitHub
parent 58d018132b
commit 06d1cc7d59
7 changed files with 792 additions and 69 deletions
+1
View File
@@ -13,5 +13,6 @@
#include "lv_cache_lru_rb.h"
#include "lv_cache_lru_ll.h"
#include "lv_cache_sc_da.h"
#endif //LV_CACHE_CLAZZ_H
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
/**
* @file lv_cache_sc_da.h
*
*/
#ifndef LV_CACHE_SC_DA_H
#define LV_CACHE_SC_DA_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../lv_cache_private.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/*************************
* GLOBAL VARIABLES
*************************/
LV_ATTRIBUTE_EXTERN_DATA extern const lv_cache_class_t lv_cache_class_sc_da;
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_CACHE_SC_DA_H*/
+2 -1
View File
@@ -11,6 +11,7 @@
#include "../lv_assert.h"
#include "lv_cache_entry_private.h"
#include "lv_cache_private.h"
#include "../lv_profiler.h"
/*********************
* DEFINES
@@ -319,7 +320,7 @@ static void cache_drop_internal_no_lock(lv_cache_t * cache, const void * key, vo
lv_cache_entry_delete(entry);
}
else {
lv_cache_entry_set_invalid(entry, true);
lv_cache_entry_set_flag(entry, LV_CACHE_ENTRY_FLAG_INVALID);
cache->clz->remove_cb(cache, entry, user_data);
}
}
+24 -16
View File
@@ -9,9 +9,7 @@
#include "lv_cache_entry.h"
#include "../../stdlib/lv_sprintf.h"
#include "../lv_assert.h"
#include "lv_cache.h"
#include "lv_cache_entry_private.h"
#include "lv_cache_private.h"
/*********************
* DEFINES
@@ -20,13 +18,7 @@
/**********************
* TYPEDEFS
**********************/
struct _lv_cache_entry_t {
const lv_cache_t * cache;
int32_t ref_cnt;
uint32_t node_size;
bool is_invalid;
};
/**********************
* STATIC PROTOTYPES
**********************/
@@ -86,16 +78,10 @@ void lv_cache_entry_set_node_size(lv_cache_entry_t * entry, uint32_t node_size)
entry->node_size = node_size;
}
void lv_cache_entry_set_invalid(lv_cache_entry_t * entry, bool is_invalid)
{
LV_ASSERT_NULL(entry);
entry->is_invalid = is_invalid;
}
bool lv_cache_entry_is_invalid(lv_cache_entry_t * entry)
{
LV_ASSERT_NULL(entry);
return entry->is_invalid;
return entry->flags & LV_CACHE_ENTRY_FLAG_INVALID;
}
void * lv_cache_entry_get_data(lv_cache_entry_t * entry)
@@ -169,17 +155,39 @@ void lv_cache_entry_init(lv_cache_entry_t * entry, const lv_cache_t * cache, con
entry->cache = cache;
entry->node_size = node_size;
entry->ref_cnt = 0;
entry->is_invalid = false;
entry->flags = 0;
}
void lv_cache_entry_delete(lv_cache_entry_t * entry)
{
LV_ASSERT_NULL(entry);
if(entry->flags & LV_CACHE_ENTRY_FLAG_DISABLE_DELETE) {
return;
}
void * data = lv_cache_entry_get_data(entry);
lv_free(data);
}
void lv_cache_entry_set_flag(lv_cache_entry_t * entry, uint8_t flags)
{
LV_ASSERT_NULL(entry);
entry->flags |= flags;
}
void lv_cache_entry_remove_flag(lv_cache_entry_t * entry, uint8_t flags)
{
LV_ASSERT_NULL(entry);
entry->flags &= (~flags);
}
bool lv_cache_entry_has_flag(lv_cache_entry_t * entry, uint8_t flags)
{
LV_ASSERT_NULL(entry);
return (entry->flags & flags) == flags;
}
/**********************
* STATIC FUNCTIONS
**********************/
+14 -4
View File
@@ -6,6 +6,7 @@
#ifndef LV_CACHE_ENTRY_PRIVATE_H
#define LV_CACHE_ENTRY_PRIVATE_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -14,8 +15,6 @@ extern "C" {
* INCLUDES
*********************/
#include "../lv_types.h"
#include "../../osal/lv_os.h"
#include "../lv_profiler.h"
/*********************
* DEFINES
@@ -25,6 +24,16 @@ extern "C" {
* TYPEDEFS
**********************/
struct _lv_cache_entry_t {
const lv_cache_t * cache;
int32_t ref_cnt;
uint32_t node_size;
#define LV_CACHE_ENTRY_FLAG_INVALID (1 << 0) /** Flag indicating if the entry is invalid and can be released */
#define LV_CACHE_ENTRY_FLAG_DISABLE_DELETE (1 << 1) /** This flag should be set if the cache class is managing the memory of the entry itself*/
#define LV_CACHE_ENTRY_FLAG_CLASS_CUSTOM (1 << 7) /**A custom flag that can be used by the different cache classes*/
uint8_t flags;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
@@ -32,11 +41,12 @@ void lv_cache_entry_reset_ref(lv_cache_entry_t * entry);
void lv_cache_entry_inc_ref(lv_cache_entry_t * entry);
void lv_cache_entry_dec_ref(lv_cache_entry_t * entry);
void lv_cache_entry_set_node_size(lv_cache_entry_t * entry, uint32_t node_size);
void lv_cache_entry_set_invalid(lv_cache_entry_t * entry, bool is_invalid);
void lv_cache_entry_set_cache(lv_cache_entry_t * entry, const lv_cache_t * cache);
void * lv_cache_entry_acquire_data(lv_cache_entry_t * entry);
void lv_cache_entry_release_data(lv_cache_entry_t * entry, void * user_data);
void lv_cache_entry_set_flag(lv_cache_entry_t * entry, uint8_t flags);
void lv_cache_entry_remove_flag(lv_cache_entry_t * entry, uint8_t flags);
bool lv_cache_entry_has_flag(lv_cache_entry_t * entry, uint8_t flags);
/*************************
* GLOBAL VARIABLES
*************************/
+192 -48
View File
@@ -3,7 +3,6 @@
#include "../lvgl.h"
#include "../../lvgl_private.h"
#include "unity/unity.h"
static uint32_t MEM_SIZE = 0;
@@ -11,18 +10,35 @@ static uint32_t MEM_SIZE = 0;
// Cache size in bytes
#define CACHE_SIZE_BYTES 1000
lv_cache_t * cache;
static lv_cache_t * cache = NULL;
typedef struct _test_data {
void setUp(void)
{
/* Function run before every test */
MEM_SIZE = lv_test_get_free_mem();
cache = NULL;
}
void tearDown(void)
{
/* Function run after every test */
if(cache) {
lv_cache_destroy(cache, NULL);
cache = NULL;
}
TEST_ASSERT_MEM_LEAK_LESS_THAN(MEM_SIZE, 64);
}
typedef struct {
lv_cache_slot_size_t slot;
void * data; // malloced data
int32_t key1;
int32_t key2;
uint32_t magic;
} test_data_t;
void * data; // malloced data
} test_data;
static lv_cache_compare_res_t compare_cb(const test_data * lhs, const test_data * rhs)
static lv_cache_compare_res_t compare_cb(const test_data_t * lhs,
const test_data_t * rhs)
{
if(lhs->key1 != rhs->key1) {
return lhs->key1 > rhs->key1 ? 1 : -1;
@@ -33,52 +49,42 @@ static lv_cache_compare_res_t compare_cb(const test_data * lhs, const test_data
return 0;
}
static void free_cb(test_data * node, void * user_data)
static void free_cb(test_data_t * node, void * user_data)
{
LV_UNUSED(user_data);
lv_free(node->data);
}
void setUp(void)
static lv_cache_t * create_cache(const lv_cache_class_t * cache_class,
size_t max_size)
{
/* Function run before every test */
MEM_SIZE = lv_test_get_free_mem();
lv_cache_ops_t ops = {
.compare_cb = (lv_cache_compare_cb_t) compare_cb,
.compare_cb = (lv_cache_compare_cb_t)compare_cb,
.create_cb = NULL,
.free_cb = (lv_cache_free_cb_t)free_cb,
};
cache = lv_cache_create(&lv_cache_class_lru_rb_size, sizeof(test_data), CACHE_SIZE_BYTES, ops);
return lv_cache_create(cache_class, sizeof(test_data_t), max_size, ops);
}
void tearDown(void)
void test_cache_lru_rb_1(void)
{
/* Function run after every test */
lv_cache_destroy(cache, NULL);
cache = NULL;
TEST_ASSERT_MEM_LEAK_LESS_THAN(MEM_SIZE, 32);
}
void test_cache_1(void)
{
cache = create_cache(&lv_cache_class_lru_rb_size, CACHE_SIZE_BYTES);
TEST_ASSERT_NOT_NULL(cache);
void * record_data_ptr = NULL;
// create many node unless cache is full
uint32_t curr_mem_size = 8;
uint32_t curr_total_mem_size = 0;
while(curr_total_mem_size < CACHE_SIZE_BYTES) {
test_data search_key = {
.slot.size = curr_mem_size,
test_data_t search_key = { .slot.size = curr_mem_size,
.key1 = (int32_t)curr_mem_size,
.key2 = (int32_t)curr_mem_size + 1
};
.key1 = (int32_t)curr_mem_size,
.key2 = (int32_t)curr_mem_size + 1
};
// acquire cache first
lv_cache_entry_t * entry = lv_cache_acquire(cache, &search_key, NULL);
lv_cache_entry_t * entry =
lv_cache_acquire(cache, &search_key, NULL);
if(entry != NULL) {
continue;
}
@@ -87,7 +93,7 @@ void test_cache_1(void)
entry = lv_cache_add(cache, &search_key, NULL);
TEST_ASSERT_NOT_NULL(entry);
test_data * data = lv_cache_entry_get_data(entry);
test_data_t * data = lv_cache_entry_get_data(entry);
TEST_ASSERT_NOT_NULL(data);
data->data = lv_malloc(data->slot.size);
@@ -102,7 +108,8 @@ void test_cache_1(void)
curr_total_mem_size += curr_mem_size;
curr_mem_size *= 2;
TEST_PRINTF("cache free: %d, allocated: %d", lv_cache_get_free_size(cache, NULL), curr_mem_size);
TEST_PRINTF("cache free: %d, allocated: %d",
lv_cache_get_free_size(cache, NULL), curr_mem_size);
}
/*
@@ -116,13 +123,11 @@ void test_cache_1(void)
/*
* Search entry {key1 = 32, key2 = 33}
*/
test_data search_key32 = {
.key1 = 32,
.key2 = 33
};
lv_cache_entry_t * entry_key32 = lv_cache_acquire(cache, &search_key32, NULL);
test_data_t search_key32 = { .key1 = 32, .key2 = 33 };
lv_cache_entry_t * entry_key32 =
lv_cache_acquire(cache, &search_key32, NULL);
test_data * cached_data_key32 = lv_cache_entry_get_data(entry_key32);
test_data_t * cached_data_key32 = lv_cache_entry_get_data(entry_key32);
TEST_ASSERT_EQUAL(record_data_ptr, cached_data_key32->data);
/*
@@ -136,19 +141,158 @@ void test_cache_1(void)
* lv_rb_note_t (4 ptr + 1 int32 may align to 8 bit on 64 bit machine) + lv_ll (2 ptr + node_size).
* Also, the def heap has some other aligned attributes. It'll also affect the final result.
*/
TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_curr_free,
sizeof(lv_rb_node_t)
+ sizeof(void *) + (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *))
+ 32); // the last 32 is an error in memory allocating
TEST_ASSERT_MEM_LEAK_LESS_THAN(
mem_curr_free,
sizeof(lv_rb_node_t) + sizeof(void *) +
(sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *)) +
32); // the last 32 is an error in memory allocating
mem_curr_free = lv_test_get_free_mem();
lv_cache_release(cache, entry_key32, NULL);
TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_curr_free,
lv_cache_entry_get_size(sizeof(test_data)) + sizeof(void *)
+ 32
+ 32);
TEST_ASSERT_MEM_LEAK_LESS_THAN(
mem_curr_free, lv_cache_entry_get_size(sizeof(test_data_t)) +
sizeof(void *) + 32 + 32);
// Now the freed cache size should be 8 + 32 = 40
TEST_ASSERT_EQUAL(40, lv_cache_get_free_size(cache, NULL));
}
void cache_add_acquire_test(void)
{
TEST_ASSERT_NOT_NULL(cache);
test_data_t expected_data[10];
for(size_t i = 0; i < 10; ++i) {
expected_data[i].key1 = i;
expected_data[i].key2 = i + 1;
lv_cache_entry_t * entry =
lv_cache_add(cache, &expected_data[i], NULL);
TEST_ASSERT_NOT_NULL(entry);
lv_cache_release(cache, entry, NULL);
}
for(size_t i = 0; i < 10; ++i) {
lv_cache_entry_t * entry =
lv_cache_acquire(cache, &expected_data[i], NULL);
TEST_ASSERT_NOT_NULL(entry);
test_data_t * actual_data = lv_cache_entry_get_data(entry);
TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1);
TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2);
lv_cache_release(cache, entry, NULL);
}
}
void cache_eviction_test(void)
{
TEST_ASSERT_NOT_NULL(cache);
test_data_t expected_data[10];
for(size_t i = 0; i < 10; ++i) {
expected_data[i].key1 = i;
expected_data[i].key2 = i + 1;
lv_cache_entry_t * entry =
lv_cache_add(cache, &expected_data[i], NULL);
TEST_ASSERT_NOT_NULL(entry);
lv_cache_release(cache, entry, NULL);
}
for(size_t i = 0; i < 10; ++i) {
lv_cache_entry_t * entry =
lv_cache_acquire(cache, &expected_data[i], NULL);
TEST_ASSERT_NOT_NULL(entry);
test_data_t * actual_data = lv_cache_entry_get_data(entry);
TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1);
TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2);
lv_cache_release(cache, entry, NULL);
}
test_data_t new_expected_entry = {
.key1 = 1000,
.key2 = 2000,
};
lv_cache_entry_t * new_entry =
lv_cache_add(cache, &new_expected_entry, NULL);
TEST_ASSERT_NOT_NULL(new_entry);
test_data_t * new_entry_data = lv_cache_entry_get_data(new_entry);
TEST_ASSERT_EQUAL(new_expected_entry.key1, new_entry_data->key1);
TEST_ASSERT_EQUAL(new_expected_entry.key2, new_entry_data->key2);
/* Check that we removed a previous entry */
size_t original_entries_found_cnt = 0;
for(size_t i = 0; i < 10; ++i) {
lv_cache_entry_t * entry =
lv_cache_acquire(cache, &expected_data[i], NULL);
if(entry != NULL) {
original_entries_found_cnt++;
lv_cache_release(cache, entry, NULL);
}
}
TEST_ASSERT_EQUAL(9, original_entries_found_cnt);
}
void test_cache_lru_rb_count_add_acquire(void)
{
cache = create_cache(&lv_cache_class_lru_rb_count, 10);
cache_add_acquire_test();
}
void test_cache_lru_ll_count_add_acquire(void)
{
cache = create_cache(&lv_cache_class_lru_ll_count, 10);
cache_add_acquire_test();
}
void test_cache_sc_da_add_acquire(void)
{
cache = create_cache(&lv_cache_class_sc_da, 10);
cache_add_acquire_test();
}
void test_cache_sc_da_eviction(void)
{
cache = create_cache(&lv_cache_class_sc_da, 10);
cache_eviction_test();
}
void test_cache_lru_rb_count_eviction(void)
{
cache = create_cache(&lv_cache_class_lru_rb_count, 10);
cache_eviction_test();
}
void test_cache_lru_ll_count_eviction(void)
{
cache = create_cache(&lv_cache_class_lru_ll_count, 10);
cache_eviction_test();
}
void test_cache_sc_da_eviction_second_chance_spares_referenced_entries(void)
{
cache = create_cache(&lv_cache_class_sc_da, 10);
TEST_ASSERT_NOT_NULL(cache);
test_data_t expected_data[10];
for(size_t i = 0; i < 10; ++i) {
expected_data[i].key1 = i;
expected_data[i].key2 = i + 1;
lv_cache_add(cache, &expected_data[i], NULL);
}
for(size_t i = 0; i < 10; ++i) {
lv_cache_entry_t * entry =
lv_cache_acquire(cache, &expected_data[i], NULL);
TEST_ASSERT_NOT_NULL(entry);
test_data_t * actual_data = lv_cache_entry_get_data(entry);
TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1);
TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2);
/* Do not release the entry so that it stays referenced*/
}
test_data_t new_expected_entry = {
.key1 = 1000,
.key2 = 2000,
};
lv_cache_entry_t * new_entry =
lv_cache_add(cache, &new_expected_entry, NULL);
TEST_ASSERT_NULL(new_entry);
}
#endif