mirror of
https://github.com/lvgl/lvgl.git
synced 2026-09-25 00:44:29 +08:00
feat(cache): implement dynamic array with second chance algorithm class (#8433)
This commit is contained in:
+1
@@ -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
|
||||
|
||||
+515
File diff suppressed because it is too large
Load Diff
+44
@@ -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*/
|
||||
Vendored
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+24
-16
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user