feat(segarray): 实现随机访问,优化迭代器和头尾访问实现逻辑,并与ringbuf对比不同应用场景下的优劣

This commit is contained in:
建峰 2026-05-16 22:18:59 +08:00
parent 115646ecd4
commit 8a65218105
9 changed files with 300 additions and 165 deletions

View File

@ -139,6 +139,14 @@ equeue ..> ringbuf : 依赖
| equeue | 队列 | 外部缓存
## 性能对比
|应用场合 |ringbuf |segarray | 说明
|----------|---------|---------|--------------
|不扩容 |内存优势 | 内存浪费| 嵌入式无堆内存
|小对象扩容|尚可 | 优秀 | 开销低,后者无拷贝更丝滑
|大对象扩容|性能较差 | 极致优秀| 前者整体搬移,后者分段扩容
## 接口函数原型
```c
// -------------------- 初始化 --------------------

View File

@ -1,11 +0,0 @@
# 日志
### 2026-05-16
### 2026-05-15
1. ringbufferresize扩容截断的处理代码简化了。
### 2026-05-14
1. darray模块insert传参obj没有判断NULL导致crash
2. darray模块reseze没有处理当realloc之后size > capacity 的情况

View File

@ -33,3 +33,58 @@ $$ memory = 2 * mapcap * sizeof(pointer) + objs:capacity $$
### 5. based on scheme 4, limited to 512 bytes
> obj_size < 512 ? segsize=512 : segsize=1
## ringbuf
对于嵌入式场景下没有malloc也即不扩容的场景需要用到的estack和equeue只选择ringbuf即可。而不使用segarray。
```c
bool ringbuf_init(struct _ringbuf *self, size_t obj_size, size_t capacity, void *mem_base);|
```
### 不扩容场景
ringbuf比segarray更节省内存。ringbuf只多一个obj_size的空间。
$$
membase:size = objsize * (capacity + 1)
$$
而segarray是不确定的所以segarray就得按照可能的最大空间分配。比如方案4的情况下
seghead默认是放中间的方便不需要库容的时候push_back和push_front一开始不用分配内存。
则为了保证push_back和push_front都能够用至少需要三个空间。
$$
mem_base_size = obj_size * (capacity * 3)
$$
当然了若非要将segarray用于嵌入式场景。也可以将seghead初始放在首段开头那么需要的总内存为
$$
mem_base_size = obj_size * (capacity * 2)
$$
**因此若嵌入式不扩容场景下ringbuf更节省内存。**
这可能也是Rust的底层VecDeque选择使用ringbuf的原因。
### 小文件库容
对于小文件ringbuf库容全内存搬移还尚可以接受。
segarray扩容开新segment然后指向新的segment性能也不差。
### 大文件扩容
但对于大文件ringbuf库容全内存搬移代价太大。
而segarray库容不会触发大量数据搬移而是新建一个segment然后指向新的segment。
**因此对于大文件扩容场景下segarray远胜于ringbuf**
## 经典问题
### 2026-05-15
1. ringbufferresize扩容截断的处理代码简化了。
### 2026-05-14
> 这就是单元测试的魅力
1. darray模块insert传参obj没有判断NULL导致crash
2. darray模块resize没有处理当realloc之后size > capacity 的情况

View File

@ -70,6 +70,7 @@ ringbuf_t ringbuf_new(size_t obj_size, size_t capacity);
void ringbuf_free(ringbuf_t* ringbuf);
#ifdef UNICSTL_STATIC_MEMORY
// mem_base_size = obj_size * (capacity + 1)
bool ringbuf_init(struct _ringbuf *self, size_t obj_size, size_t capacity, void *mem_base);
#endif

View File

@ -33,8 +33,7 @@ struct _segarray
size_t _segsize;
ringbuf_t _map;
size_t _maphead;
size_t _maptail;
ringbuf_t _mapfree;
size_t _seghead;
size_t _segtail;
@ -50,6 +49,11 @@ struct _segarray
bool (*back)(struct _segarray* self, void* obj);
bool (*front)(struct _segarray* self, void* obj);
// -------------------- random access --------------------
bool (*set)(struct _segarray *self, size_t index, const void *obj); // O(1)
bool (*get)(struct _segarray *self, size_t index, void *obj); // O(1)
const void* (*at)(struct _segarray *self, size_t index); // O(1)
// base
bool (*resize)(struct _segarray *self, size_t capacity);
size_t (*size)(struct _segarray* self);

View File

@ -1,15 +1,16 @@
/**
* @file unicstl_config.h
* @author wenjf (Orig5826@163.com)
* @brief
* @brief
* @version 0.1
* @date 2025-04-22
*
*
* @copyright Copyright (c) 2025
*
*
*/
#ifndef _UNICSTL_CONFIG_H_
#define _UNICSTL_CONFIG_H_
// clang-format off
// #define NDEBUG // release mode if define
#ifndef NDEBUG
@ -84,4 +85,5 @@
#endif // UNICSTL_DEBUG
#endif // _UNICSTL_CONFIG_H_
// clang-format on
#endif // _UNICSTL_CONFIG_H_

View File

@ -28,37 +28,40 @@
#include "iterator.h"
#include "logger.h"
#define UNICSTL_VERSION_MAJOR 0
#define UNICSTL_VERSION_MINOR 0
#define UNICSTL_VERSION_MICRO 10
#define UNICSTL_VERSION ((UNICSTL_VERSION_MAJOR << 16) | (UNICSTL_VERSION_MINOR << 8) | UNICSTL_VERSION_MICRO)
#define UNICSTL_TOSTRING_(x) #x
#define UNICSTL_TOSTRING(x) UNICSTL_TOSTRING_(x)
#define UNICSTL_VERSION_STRING UNICSTL_TOSTRING(UNICSTL_VERSION_MAJOR) "." UNICSTL_TOSTRING(UNICSTL_VERSION_MINOR) "." UNICSTL_TOSTRING(UNICSTL_VERSION_MICRO)
#define UNICSTL_UNUSED(x) (void)(x)
// clang-format off
#define UNICSTL_VERSION_MAJOR 0
#define UNICSTL_VERSION_MINOR 0
#define UNICSTL_VERSION_MICRO 10
#define UNICSTL_VERSION ((UNICSTL_VERSION_MAJOR << 16) | (UNICSTL_VERSION_MINOR << 8) | UNICSTL_VERSION_MICRO)
#define UNICSTL_TOSTRING_(x) #x
#define UNICSTL_TOSTRING(x) UNICSTL_TOSTRING_(x)
#define UNICSTL_VERSION_STRING UNICSTL_TOSTRING(UNICSTL_VERSION_MAJOR) "." \
UNICSTL_TOSTRING(UNICSTL_VERSION_MINOR) "." \
UNICSTL_TOSTRING(UNICSTL_VERSION_MICRO)
/**
* @brief default capacity and ratio
*
*/
#ifndef UNICSTL_CAPACITY_INIT
#define UNICSTL_CAPACITY_INIT 8 // 若capacity参数为0时自动分配默认初始容量
#define UNICSTL_CAPACITY_INIT 8 // 若capacity参数为0时自动分配默认初始容量
#endif
#ifndef UNICSTL_CAPACITY_MAX
#define UNICSTL_CAPACITY_MAX 8192 // 最大容量
#define UNICSTL_CAPACITY_MAX 8192 // 最大容量
#endif
#ifndef UNICSTL_OBJSIZE_MAX
#define UNICSTL_OBJSIZE_MAX 8192 // 最大对象大小
#define UNICSTL_OBJSIZE_MAX 8192 // 最大对象大小
#endif
#ifndef UNICSTL_STATIC_MEMORY
#define UNICSTL_STATIC_MEMORY
#endif
#define UNICSTL_UNUSED(x) (void)(x)
// clang-format on
/**
* @brief assert function
*
@ -78,7 +81,8 @@ extern void _unicstl_assert(const char *expr, const char *file, int line);
#else
#define unicstl_assert(expr) // assert(expr)
#endif // UNICSTL_ASSERT_ENABLE
#endif // UNICSTL_ASSERT_ENABLE
/**
* @brief malloc and free function
@ -115,7 +119,7 @@ extern void unicstl_free(void *ptr);
static inline void *unicstl_malloc(size_t size) { return NULL; }
static inline void *unicstl_calloc(size_t num, size_t size) { return NULL; }
static inline void *unicstl_realloc(void *ptr, size_t size) { return NULL; }
static inline void unicstl_free(void *ptr) { }
static inline void unicstl_free(void *ptr) {}
#endif // UNICSTL_MALLOC_ENABLE
static inline const void *obj_at(const void *objs, size_t index, size_t obj_size)

View File

@ -20,15 +20,6 @@ static inline size_t segarray_map_full(struct _segarray *self, size_t capacity)
return 0;
}
static inline void print_pos(struct _segarray *self)
{
log_debug("head[%d][%d], tail[%d][%d]\n",
self->_maphead,
self->_seghead,
self->_maptail - 1,
self->_segtail);
}
static bool segarray_push_back(struct _segarray *self, const void *obj)
{
unicstl_assert(self != NULL);
@ -38,34 +29,47 @@ static bool segarray_push_back(struct _segarray *self, const void *obj)
}
ringbuf_t map = self->_map;
ringbuf_t mapfree = self->_mapfree;
if (self->_segtail == self->_segsize)
{
if (map->full(map))
if(self->_mapfree->empty(mapfree))
{
log_warn("map->full");
return false;
// size_t new_capacity = unicstl_new_capacity(self->capacity(self));
// if (!map->resize(map, new_capacity))
// {
// log_error("map->resize(map, new_capacity) failed!");
// return false;
// }
if (map->full(map))
{
// need resize
log_error("next, you need to realize it");
return false;
}
else
{
rawbuf_t seg = rawbuf_new(self->_obj_size, self->_capacity);
if (seg == NULL)
{
log_error("rawbuf_new failed!");
return false;
}
map->push_back(map, &seg);
self->_segtail = 0;
}
}
rawbuf_t seg = rawbuf_new(self->_obj_size, self->_capacity);
if (seg == NULL)
else
{
log_error("rawbuf_new failed!");
return false;
rawbuf_t seg;
if (!mapfree->pop_back(mapfree, &seg))
{
log_error("mapfree->pop_back failed!");
return false;
}
if (!map->push_back(map, &seg))
{
log_error("map->push_back failed!");
return false;
}
self->_seghead = 0;
}
map->push_back(map, &seg);
self->_maptail = self->_maptail + 1;
self->_segtail = 0;
}
print_pos(self);
rawbuf_t seg;
if (!map->back(map, &seg))
{
@ -94,35 +98,47 @@ static bool segarray_push_front(struct _segarray *self, const void *obj)
}
ringbuf_t map = self->_map;
ringbuf_t mapfree = self->_mapfree;
if (self->_seghead == 0)
{
log_debug("map->size = %d\n", map->size(map));
if (map->full(map))
if(self->_mapfree->empty(mapfree))
{
log_warn("map->full");
return false;
// size_t new_capacity = unicstl_new_capacity(self->capacity(self));
// if (!map->resize(map, new_capacity))
// {
// log_error("map->resize(map, new_capacity) failed!");
// return false;
// }
if (map->full(map))
{
// need resize
log_error("next, you need to realize it");
return false;
}
else
{
rawbuf_t seg = rawbuf_new(self->_obj_size, self->_capacity);
if (seg == NULL)
{
log_error("rawbuf_new failed!");
return false;
}
map->push_front(map, &seg);
self->_seghead = self->_segsize;
}
}
rawbuf_t seg = rawbuf_new(self->_obj_size, self->_capacity);
if (seg == NULL)
else
{
log_error("rawbuf_new failed!");
return false;
rawbuf_t seg;
if (!mapfree->pop_back(mapfree, &seg))
{
log_error("mapfree->pop_back failed!");
return false;
}
if (!map->push_front(map, &seg))
{
log_error("map->push_back failed!");
return false;
}
self->_seghead = self->_segsize;
}
map->push_front(map, &seg);
self->_maphead = self->_maphead - 1;
self->_seghead = self->_segsize;
}
print_pos(self);
rawbuf_t seg;
if (!map->front(map, &seg))
{
@ -150,9 +166,8 @@ static bool segarray_pop_back(struct _segarray *self, void *obj)
return false;
}
print_pos(self);
ringbuf_t map = self->_map;
ringbuf_t mapfree = self->_mapfree;
rawbuf_t seg;
size_t index = self->_segtail - 1;
@ -173,10 +188,13 @@ static bool segarray_pop_back(struct _segarray *self, void *obj)
if(self->_segtail == 0)
{
self->_segtail = self->_segsize;
self->_maptail = self->_maptail - 1;
map->pop_back(map, &seg);
rawbuf_free(&seg);
if (!mapfree->push_back(mapfree, &seg))
{
log_error("mapfree->push_back failed!");
return false;
}
}
log_info("pop_back success!");
@ -197,9 +215,9 @@ static bool segarray_pop_front(struct _segarray *self, void *obj)
return false;
}
print_pos(self);
ringbuf_t map = self->_map;
ringbuf_t mapfree = self->_mapfree;
size_t index = self->_seghead;
if(obj != NULL)
{
@ -219,11 +237,15 @@ static bool segarray_pop_front(struct _segarray *self, void *obj)
if(self->_seghead == self->_segsize)
{
self->_seghead = 0;
self->_maphead = self->_maphead + 1;
rawbuf_t seg;
map->pop_front(map, &seg);
rawbuf_free(&seg);
if (!mapfree->push_back(mapfree, &seg))
{
log_error("mapfree->push_back failed!");
return false;
}
}
log_info("pop_front success!");
@ -238,15 +260,7 @@ static bool segarray_back(struct _segarray *self, void *obj)
{
return false;
}
ringbuf_t map = self->_map;
rawbuf_t seg;
if (!map->back(map, &seg))
{
return false;
}
size_t index = self->_segtail - 1;
return seg->get(seg, index, obj);
return self->get(self, self->size(self) - 1, obj);
}
static bool segarray_front(struct _segarray *self, void *obj)
@ -256,15 +270,95 @@ static bool segarray_front(struct _segarray *self, void *obj)
{
return false;
}
return self->get(self, 0, obj);
}
static bool segarray_calc_index(struct _segarray *self, size_t index, size_t *map_index, size_t *seg_index)
{
ringbuf_t map = self->_map;
rawbuf_t seg;
if (!map->front(map, &seg))
rawbuf_t seg = NULL;
size_t map_idx;
size_t seg_idx;
size_t seg1_left = self->_segsize - self->_seghead;
if(index < seg1_left)
{
map_idx = 0;
seg_idx = self->_seghead + index;
}
else
{
size_t index_left = index - seg1_left;
map_idx = index_left / self->_segsize + 1;
seg_idx = index_left % self->_segsize;
}
*map_index = map_idx;
*seg_index = seg_idx;
return true;
}
static bool segarray_set(struct _segarray *self, size_t index, const void *obj)
{
unicstl_assert(self != NULL);
if (index >= self->size(self) || obj == NULL)
{
return false;
}
size_t index = self->_seghead;
return seg->get(seg, index, obj);
ringbuf_t map = self->_map;
rawbuf_t seg = NULL;
size_t map_index;
size_t seg_index;
segarray_calc_index(self, index, &map_index, &seg_index);
if (!map->get(map, map_index, &seg))
{
return false;
}
return seg->set(seg, seg_index, obj);
}
static bool segarray_get(struct _segarray *self, size_t index, void *obj)
{
unicstl_assert(self != NULL);
if (index >= self->size(self) || obj == NULL)
{
return false;
}
ringbuf_t map = self->_map;
rawbuf_t seg = NULL;
size_t map_index;
size_t seg_index;
segarray_calc_index(self, index, &map_index, &seg_index);
if (!map->get(map, map_index, &seg))
{
return false;
}
return seg->get(seg, seg_index, obj);
}
static const void* segarray_at(struct _segarray *self, size_t index)
{
unicstl_assert(self != NULL);
if (index >= self->size(self))
{
return false;
}
ringbuf_t map = self->_map;
rawbuf_t seg = NULL;
size_t map_index;
size_t seg_index;
segarray_calc_index(self, index, &map_index, &seg_index);
if (!map->get(map, map_index, &seg))
{
return false;
}
return seg->at(seg, seg_index);
}
static bool segarray_resize(struct _segarray *self, size_t capacity)
@ -280,6 +374,7 @@ static bool segarray_resize(struct _segarray *self, size_t capacity)
return false;
}
log_error("you need to implement this function!");
return true;
}
@ -305,9 +400,14 @@ static bool segarray_full(struct _segarray *self)
{
unicstl_assert(self != NULL);
size_t map_size = self->_map->size(self->_map);
return (self->_maphead == 0 && self->_seghead == 0) ||
(self->_maptail == map_size - 1 && self->_segtail == self->_segsize - 1);
if(self->_map->full(self->_map))
{
if(self->_seghead == 0 || self->_segtail == self->_segsize - 1)
{
return true;
}
}
return false;
}
static bool segarray_clear(struct _segarray *self)
@ -317,7 +417,6 @@ static bool segarray_clear(struct _segarray *self)
self->_seghead = clac_start_index(self->_segsize);
self->_segtail = self->_seghead;
self->_maptail = self->_maphead;
return true;
}
@ -342,10 +441,17 @@ static void segarray_destory(struct _segarray *self)
static void segarray_print(struct _segarray *self)
{
unicstl_assert(self != NULL);
const void *obj = NULL;
for (size_t i = 0; i < self->size(self); i++)
{
self->print_obj(obj_at(self->obj, self->_obj_size, i));
obj = self->at(self, i);
if(obj == NULL)
{
log_error("objs[%ld] is NULL", i);
return;
}
self->print_obj(obj);
}
}
@ -353,7 +459,6 @@ bool segarray_iter_hasnext(struct _iterator *iter)
{
unicstl_assert(iter != NULL);
unicstl_assert(iter->_container != NULL);
segarray_t self = (segarray_t)iter->_container;
if (iter->_order == SEGARRAY_FORWARD)
@ -377,52 +482,10 @@ const void *segarray_iter_next(struct _iterator *iter)
{
unicstl_assert(iter != NULL);
unicstl_assert(iter->_container != NULL);
segarray_t self = (segarray_t)iter->_container;
const void *obj = NULL;
size_t index = iter->_index;
ringbuf_t map = self->_map;
rawbuf_t seg = NULL;
size_t map_index;
size_t seg_index;
if (iter->_order == SEGARRAY_REVERSE)
{
log_debug("index:%ld", index);
index = self->size(self) + 1 - index;
log_debug("reverse-index:%ld", index);
}
size_t seg1_left = self->_segsize - self->_seghead;
if(index < seg1_left)
{
map_index = 0;
seg_index = self->_seghead + index;
}
else
{
size_t index_left = index - seg1_left;
map_index = index_left / self->_segsize + 1;
seg_index = index_left % self->_segsize;
}
log_debug("index:%ld, sethead:%ld", index, seg_index);
log_debug("map_index:%ld, seg_index:%ld", map_index, seg_index);
if (map->get(map, map_index, &seg))
{
obj = seg->at(seg, seg_index);
if(obj == NULL)
{
return NULL;
}
}
else
{
return NULL;
}
obj = self->at(self, iter->_index);
if (iter->_order == SEGARRAY_FORWARD)
{
iter->_index++;
@ -443,14 +506,13 @@ iterator_t segarray_iter(struct _segarray *self, enum _segarray_order order)
iter->_index = 0;
iter->_order = order;
//..
if (iter->_order == SEGARRAY_FORWARD)
{
iter->_index = 0;
}
else
{
iter->_index = self->_map->size(self->_map);
iter->_index = self->size(self) - 1;
}
iter->hasnext = segarray_iter_hasnext;
@ -494,10 +556,18 @@ bool segarray_init(struct _segarray *self, size_t obj_size, size_t capacity, voi
return false;
}
self->_mapfree = ringbuf_new(sizeof(rawbuf_t), 8);
if (self->_mapfree == NULL)
{
ringbuf_free(&self->_map);
return false;
}
rawbuf_t seg = rawbuf_new(obj_size, self->_segsize);
if (seg == NULL)
{
ringbuf_free(&self->_map);
ringbuf_free(&self->_mapfree);
return false;
}
// config first obj index in seg array
@ -505,8 +575,6 @@ bool segarray_init(struct _segarray *self, size_t obj_size, size_t capacity, voi
self->_segtail = self->_seghead;
// add first seg array to map
self->_maphead = 0;
self->_maptail = 1;
self->_map->push_back(self->_map, &seg);
}
@ -521,6 +589,11 @@ bool segarray_init(struct _segarray *self, size_t obj_size, size_t capacity, voi
self->back = segarray_back;
self->front = segarray_front;
// -------------------- random access --------------------
self->set = segarray_set;
self->get = segarray_get;
self->at = segarray_at;
// base
self->resize = segarray_resize;
self->size = segarray_size;

View File

@ -372,7 +372,6 @@ static void test_segarray_front_invalid(void)
segarray_free(&segarray);
}
#if 0
static void test_segarray_set(void)
{
int temp = 0;
@ -475,7 +474,6 @@ static void test_segarray_at(void)
segarray_free(&segarray);
}
#endif
static void test_segarray_iter(void)
{
@ -767,11 +765,12 @@ static void test_segarray_struct(void)
TEST_ASSERT_FALSE(segarray->back(segarray, &temp));
}
}
TEST_ASSERT_TRUE(segarray->empty(segarray));
for (i = 0; i < len; i++)
{
TEST_ASSERT_TRUE(segarray->push_front(segarray, &data[i]));
TEST_ASSERT_TRUE(segarray->front(segarray, &temp));
TEST_ASSERT_EQUAL_INT(data[i].id, temp.id);
TEST_ASSERT_EQUAL_STRING(data[i].name, temp.name);
@ -855,13 +854,13 @@ void test_segarray(void)
RUN_TEST(test_segarray_front_invalid);
// ---------- random access ----------
// RUN_TEST(test_segarray_set);
// RUN_TEST(test_segarray_set_invalid);
RUN_TEST(test_segarray_set);
RUN_TEST(test_segarray_set_invalid);
// RUN_TEST(test_segarray_at);
RUN_TEST(test_segarray_at);
// ---------- base ----------
// RUN_TEST(test_segarray_iter);
RUN_TEST(test_segarray_iter);
// RUN_TEST(test_segarray_resize);
// RUN_TEST(test_segarray_resize_invalid);
@ -869,8 +868,8 @@ void test_segarray(void)
// RUN_TEST(test_segarray_dynamic);
// RUN_TEST(test_segarray_status);
RUN_TEST(test_segarray_status);
// ---------- ext ----------
// RUN_TEST(test_segarray_struct);
RUN_TEST(test_segarray_struct);
}