This reverts commit d3a97d5ef8.
This commit is contained in:
parent
76a41c8166
commit
2f0dcd65b2
4 changed files with 437 additions and 426 deletions
|
|
@ -10,8 +10,6 @@ extern "C" {
|
|||
|
||||
typedef void (*RzListFree)(void *ptr);
|
||||
|
||||
#define RZ_LIST_SLAB_SIZE 256
|
||||
|
||||
typedef struct rz_list_iter_t RzListIter;
|
||||
|
||||
struct rz_list_iter_t {
|
||||
|
|
@ -20,23 +18,12 @@ struct rz_list_iter_t {
|
|||
RzListIter *prev;
|
||||
};
|
||||
|
||||
typedef struct rz_list_slab_t {
|
||||
RzListIter nodes[RZ_LIST_SLAB_SIZE];
|
||||
struct rz_list_slab_t *next_slab;
|
||||
} RzListSlab;
|
||||
|
||||
typedef struct rz_list_pool_t {
|
||||
RzListSlab *slabs;
|
||||
RzListIter *freelist;
|
||||
} RzListPool;
|
||||
|
||||
typedef struct rz_list_t {
|
||||
RzListIter *head;
|
||||
RzListIter *tail;
|
||||
RzListFree free;
|
||||
ut32 length;
|
||||
bool sorted;
|
||||
RzListPool *pool;
|
||||
} RzList;
|
||||
|
||||
// RzListComparator should return -1, 0, 1 to indicate "value < list_data", "value == list_data", "value > list_data".
|
||||
|
|
|
|||
|
|
@ -5,90 +5,6 @@
|
|||
#include <stdio.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
static void rz_list_pool_free(RzListPool *pool) {
|
||||
if (!pool) {
|
||||
return;
|
||||
}
|
||||
RzListSlab *slab = pool->slabs;
|
||||
while (slab) {
|
||||
RzListSlab *next = slab->next_slab;
|
||||
free(slab);
|
||||
slab = next;
|
||||
}
|
||||
free(pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Allocates an RzListIter from the pool, growing by one slab if needed.
|
||||
* If \p pool is NULL, falls back to a plain heap allocation.
|
||||
*/
|
||||
static inline RzListIter *pool_alloc_node(RzListPool *pool) {
|
||||
if (!pool) {
|
||||
return RZ_NEW0(RzListIter);
|
||||
}
|
||||
if (!pool->freelist) {
|
||||
RzListSlab *slab = RZ_NEW0(RzListSlab);
|
||||
if (!slab) {
|
||||
return NULL;
|
||||
}
|
||||
slab->next_slab = pool->slabs;
|
||||
pool->slabs = slab;
|
||||
for (int i = 0; i < RZ_LIST_SLAB_SIZE - 1; i++) {
|
||||
slab->nodes[i].next = &slab->nodes[i + 1];
|
||||
}
|
||||
slab->nodes[RZ_LIST_SLAB_SIZE - 1].next = NULL;
|
||||
pool->freelist = &slab->nodes[0];
|
||||
}
|
||||
RzListIter *n = pool->freelist;
|
||||
pool->freelist = n->next;
|
||||
n->next = NULL;
|
||||
n->prev = NULL;
|
||||
n->val = NULL;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns an RzListIter node back to the pool freelist so it can be
|
||||
* reused, without freeing any heap memory.
|
||||
* If \p pool is NULL, the node was heap-allocated and is freed directly.
|
||||
*/
|
||||
static inline void pool_return_node(RzListPool *pool, RzListIter *node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
if (!pool) {
|
||||
free(node);
|
||||
return;
|
||||
}
|
||||
node->val = NULL;
|
||||
node->prev = NULL;
|
||||
node->next = pool->freelist;
|
||||
pool->freelist = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the RzListIter at position \p n, traversing from whichever
|
||||
* end is closer. Returns NULL if \p n is out of bounds.
|
||||
**/
|
||||
static inline RzListIter *rz_list_iter_at(const RzList *list, ut32 n) {
|
||||
if (n >= list->length) {
|
||||
return NULL;
|
||||
}
|
||||
RzListIter *it;
|
||||
if (n < list->length / 2) {
|
||||
it = list->head;
|
||||
for (ut32 i = 0; i < n; i++) {
|
||||
it = it->next;
|
||||
}
|
||||
} else {
|
||||
it = list->tail;
|
||||
for (ut32 i = list->length - 1; i > n; i--) {
|
||||
it = it->prev;
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief returns the value stored in the prev RzList iterator
|
||||
*
|
||||
|
|
@ -128,7 +44,7 @@ RZ_API bool rz_list_iter_swap_data(RZ_NONNULL RzListIter *iter0, RZ_NONNULL RzLi
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief returns the first RzList iterator in the list
|
||||
* \brief returns the first RzList iterator int the list
|
||||
*
|
||||
**/
|
||||
RZ_API RZ_BORROW RzListIter *rz_list_iterator(RZ_NONNULL const RzList *list) {
|
||||
|
|
@ -174,7 +90,6 @@ RZ_API void rz_list_init(RZ_NONNULL RzList *list) {
|
|||
list->free = NULL;
|
||||
list->length = 0;
|
||||
list->sorted = false;
|
||||
list->pool = RZ_NEW0(RzListPool);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -189,23 +104,21 @@ RZ_API ut32 rz_list_length(RZ_NONNULL const RzList *list) {
|
|||
}
|
||||
|
||||
static void _list_purge_with_free(RzList *list) {
|
||||
RzListPool *pool = list->pool;
|
||||
RzListIter *it = list->head;
|
||||
RzListFree fn = list->free;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
fn(it->val);
|
||||
pool_return_node(pool, it);
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void _list_purge_no_free(RzList *list) {
|
||||
RzListPool *pool = list->pool;
|
||||
RzListIter *it = list->head;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
pool_return_node(pool, it);
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +149,6 @@ RZ_API void rz_list_free(RZ_NULLABLE RzList *list) {
|
|||
return;
|
||||
}
|
||||
rz_list_purge(list);
|
||||
rz_list_pool_free(list->pool);
|
||||
free(list);
|
||||
}
|
||||
|
||||
|
|
@ -277,61 +189,32 @@ RZ_API void rz_list_delete(RZ_NONNULL RzList *list, RZ_OWN RZ_NONNULL RzListIter
|
|||
list->free(iter->val);
|
||||
}
|
||||
iter->val = NULL;
|
||||
pool_return_node(list->pool, iter);
|
||||
free(iter);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Joins list2 into list1 by copying all values; list2 is left empty
|
||||
* but its structure (pool, free pointer) is preserved.
|
||||
* The caller is responsible for freeing list2 afterward.
|
||||
* \brief Joins 2 list into one (list2 pointer needs to be freed by the user)
|
||||
*
|
||||
* \note list2's free callback is NOT called during the join so that values
|
||||
* transferred to list1 are not prematurely destroyed.
|
||||
**/
|
||||
RZ_API bool rz_list_join(RZ_NONNULL RzList *list1, RZ_NONNULL RzList *list2) {
|
||||
rz_return_val_if_fail(list1 && list2, false);
|
||||
if (!list2->length) {
|
||||
rz_return_val_if_fail(list1 && list2, 0);
|
||||
|
||||
if (!(list2->length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RzListPool *pool1 = list1->pool;
|
||||
RzListIter *it = list2->head;
|
||||
while (it) {
|
||||
RzListIter *n = pool_alloc_node(pool1);
|
||||
if (!n) {
|
||||
return false;
|
||||
}
|
||||
n->val = it->val;
|
||||
n->prev = list1->tail;
|
||||
n->next = NULL;
|
||||
if (list1->tail) {
|
||||
list1->tail->next = n;
|
||||
}
|
||||
list1->tail = n;
|
||||
if (!list1->head) {
|
||||
list1->head = n;
|
||||
}
|
||||
list1->length++;
|
||||
it = it->next;
|
||||
if (!(list1->length)) {
|
||||
list1->head = list2->head;
|
||||
list1->tail = list2->tail;
|
||||
} else {
|
||||
list1->tail->next = list2->head;
|
||||
list2->head->prev = list1->tail;
|
||||
list1->tail = list2->tail;
|
||||
list1->tail->next = NULL;
|
||||
list1->sorted = false;
|
||||
}
|
||||
|
||||
list1->sorted = false;
|
||||
|
||||
/*
|
||||
* Walk list2's nodes and return each one to list2's pool (or free them
|
||||
* if list2 has no pool). We must NOT call list2->free on the values
|
||||
* because ownership of the values has just been transferred to list1.
|
||||
*/
|
||||
RzListIter *cur = list2->head;
|
||||
while (cur) {
|
||||
RzListIter *next = cur->next;
|
||||
pool_return_node(list2->pool, cur);
|
||||
cur = next;
|
||||
}
|
||||
list2->head = NULL;
|
||||
list2->tail = NULL;
|
||||
list1->length += list2->length;
|
||||
list2->length = 0;
|
||||
|
||||
list2->head = list2->tail = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -403,7 +286,7 @@ RZ_API RZ_OWN RzList *rz_list_new_from_iterator(RZ_BORROW RZ_NONNULL RzIterator
|
|||
*
|
||||
**/
|
||||
RZ_API RZ_OWN RzListIter *rz_list_item_new(RZ_NULLABLE void *data) {
|
||||
RzListIter *item = pool_alloc_node(NULL);
|
||||
RzListIter *item = RZ_NEW0(RzListIter);
|
||||
if (item) {
|
||||
item->val = data;
|
||||
}
|
||||
|
|
@ -419,7 +302,7 @@ RZ_API RZ_BORROW RzListIter *rz_list_append(RZ_NONNULL RzList *list, RZ_NONNULL
|
|||
|
||||
rz_return_val_if_fail(list, NULL);
|
||||
|
||||
item = pool_alloc_node(list->pool);
|
||||
item = RZ_NEW(RzListIter);
|
||||
if (!item) {
|
||||
return item;
|
||||
}
|
||||
|
|
@ -445,7 +328,7 @@ RZ_API RZ_BORROW RzListIter *rz_list_append(RZ_NONNULL RzList *list, RZ_NONNULL
|
|||
RZ_API RZ_BORROW RzListIter *rz_list_prepend(RZ_NONNULL RzList *list, RZ_NONNULL void *data) {
|
||||
rz_return_val_if_fail(list, NULL);
|
||||
|
||||
RzListIter *item = pool_alloc_node(list->pool);
|
||||
RzListIter *item = RZ_NEW0(RzListIter);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -469,30 +352,33 @@ RZ_API RZ_BORROW RzListIter *rz_list_prepend(RZ_NONNULL RzList *list, RZ_NONNULL
|
|||
*
|
||||
**/
|
||||
RZ_API RZ_BORROW RzListIter *rz_list_insert(RZ_NONNULL RzList *list, ut32 n, RZ_NONNULL void *data) {
|
||||
RzListIter *it, *item;
|
||||
ut32 i;
|
||||
|
||||
rz_return_val_if_fail(list, NULL);
|
||||
|
||||
if (!list->head || !n) {
|
||||
return rz_list_prepend(list, data);
|
||||
}
|
||||
RzListIter *it = rz_list_iter_at(list, n);
|
||||
if (!it) {
|
||||
return rz_list_append(list, data);
|
||||
for (it = list->head, i = 0; it && it->val; it = it->next, i++) {
|
||||
if (i == n) {
|
||||
item = RZ_NEW(RzListIter);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
item->val = data;
|
||||
item->next = it;
|
||||
item->prev = it->prev;
|
||||
if (it->prev) {
|
||||
it->prev->next = item;
|
||||
}
|
||||
it->prev = item;
|
||||
list->length++;
|
||||
list->sorted = true;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
RzListIter *item = pool_alloc_node(list->pool);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
item->val = data;
|
||||
item->next = it;
|
||||
item->prev = it->prev;
|
||||
if (it->prev) {
|
||||
it->prev->next = item;
|
||||
} else {
|
||||
list->head = item;
|
||||
}
|
||||
it->prev = item;
|
||||
list->length++;
|
||||
list->sorted = false;
|
||||
return item;
|
||||
return rz_list_append(list, data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -514,7 +400,7 @@ RZ_API RZ_OWN void *rz_list_pop(RZ_NONNULL RzList *list) {
|
|||
list->tail->next = NULL;
|
||||
}
|
||||
data = iter->val;
|
||||
pool_return_node(list->pool, iter);
|
||||
free(iter);
|
||||
list->length--;
|
||||
}
|
||||
return data;
|
||||
|
|
@ -538,7 +424,7 @@ RZ_API RZ_OWN void *rz_list_pop_head(RZ_NONNULL RzList *list) {
|
|||
list->head->prev = NULL;
|
||||
}
|
||||
data = iter->val;
|
||||
pool_return_node(list->pool, iter);
|
||||
free(iter);
|
||||
list->length--;
|
||||
}
|
||||
return data;
|
||||
|
|
@ -549,13 +435,30 @@ RZ_API RZ_OWN void *rz_list_pop_head(RZ_NONNULL RzList *list) {
|
|||
*
|
||||
**/
|
||||
RZ_API ut32 rz_list_del_n(RZ_NONNULL RzList *list, ut32 n) {
|
||||
RzListIter *it;
|
||||
ut32 i;
|
||||
|
||||
rz_return_val_if_fail(list, false);
|
||||
RzListIter *it = rz_list_iter_at(list, n);
|
||||
if (!it) {
|
||||
return false;
|
||||
|
||||
for (it = list->head, i = 0; it && it->val; it = it->next, i++) {
|
||||
if (i == n) {
|
||||
if (!it->prev && !it->next) {
|
||||
list->head = list->tail = NULL;
|
||||
} else if (!it->prev) {
|
||||
it->next->prev = NULL;
|
||||
list->head = it->next;
|
||||
} else if (!it->next) {
|
||||
it->prev->next = NULL;
|
||||
list->tail = it->prev;
|
||||
} else {
|
||||
it->prev->next = it->next;
|
||||
it->next->prev = it->prev;
|
||||
}
|
||||
rz_list_delete(list, it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
rz_list_delete(list, it);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -567,7 +470,7 @@ RZ_API void rz_list_reverse(RZ_NONNULL RzList *list) {
|
|||
|
||||
rz_return_if_fail(list);
|
||||
|
||||
for (it = list->head; it; it = it->prev) {
|
||||
for (it = list->head; it && it->val; it = it->prev) {
|
||||
tmp = it->prev;
|
||||
it->prev = it->next;
|
||||
it->next = tmp;
|
||||
|
|
@ -610,7 +513,7 @@ RZ_API RZ_BORROW RzListIter *rz_list_add_sorted(RZ_NONNULL RzList *list, RZ_NONN
|
|||
for (it = list->head; it && it->val && cmp(data, it->val, user) > 0; it = it->next) {
|
||||
}
|
||||
if (it) {
|
||||
item = pool_alloc_node(list->pool);
|
||||
item = RZ_NEW0(RzListIter);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -636,17 +539,21 @@ RZ_API RZ_BORROW RzListIter *rz_list_add_sorted(RZ_NONNULL RzList *list, RZ_NONN
|
|||
*
|
||||
**/
|
||||
RZ_API ut32 rz_list_set_n(RZ_NONNULL RzList *list, ut32 n, RZ_NONNULL void *data) {
|
||||
RzListIter *it;
|
||||
ut32 i;
|
||||
|
||||
rz_return_val_if_fail(list, false);
|
||||
RzListIter *it = rz_list_iter_at(list, n);
|
||||
if (!it) {
|
||||
return false;
|
||||
for (it = list->head, i = 0; it; it = it->next, i++) {
|
||||
if (i == n) {
|
||||
if (list->free) {
|
||||
list->free(it->val);
|
||||
}
|
||||
it->val = data;
|
||||
list->sorted = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (list->free) {
|
||||
list->free(it->val);
|
||||
}
|
||||
it->val = data;
|
||||
list->sorted = false;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -654,9 +561,20 @@ RZ_API ut32 rz_list_set_n(RZ_NONNULL RzList *list, ut32 n, RZ_NONNULL void *data
|
|||
*
|
||||
**/
|
||||
RZ_API RZ_BORROW void *rz_list_get_n(RZ_NONNULL const RzList *list, ut32 n) {
|
||||
RzListIter *it;
|
||||
ut32 i;
|
||||
|
||||
rz_return_val_if_fail(list, NULL);
|
||||
RzListIter *it = rz_list_iter_at(list, n);
|
||||
return it ? it->val : NULL;
|
||||
if (n >= list->length) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (it = list->head, i = 0; it && it->val; it = it->next, i++) {
|
||||
if (i == n) {
|
||||
return it->val;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -869,7 +787,7 @@ RZ_API void rz_list_sorted_uniq(RZ_NONNULL RzList *list, RZ_NONNULL RzListCompar
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief Casts a RzList containing strings into a concatenated string
|
||||
* \brief Casts a RzList containg strings into a concatenated string
|
||||
*
|
||||
* \param list The list of strings to concatenate.
|
||||
* \param ch Char to separate the match strings.
|
||||
|
|
|
|||
|
|
@ -1,230 +1,375 @@
|
|||
// SPDX-FileCopyrightText: 2026 Farhan-25 <shadowfinder1799@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include "bench_utils.h"
|
||||
#include <rz_list.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
/**
|
||||
* \file bench_list.c
|
||||
* \brief Benchmarks for core RzList operations.
|
||||
* \brief Benchmarks for `RzList` functions - comparing rz_list_purge implementations
|
||||
*/
|
||||
|
||||
static void _fill(RzList *l, int count) {
|
||||
for (int j = 0; j < count; j++) {
|
||||
rz_list_append(l, (void *)(intptr_t)j);
|
||||
// Helper to populate a list with n non-null elements
|
||||
static void populate_list(RzList *list, ut32 n) {
|
||||
for (ut32 j = 0; j < n; ++j) {
|
||||
rz_list_append(list, (void *)(size_t)(j + 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_append(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[append] 10k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 10000);
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[append] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[append] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000000);
|
||||
rz_list_free(l);
|
||||
});
|
||||
// Helper to populate a list with n heap-allocated elements (for free fn tests)
|
||||
static void populate_list_alloc(RzList *list, ut32 n) {
|
||||
for (ut32 j = 0; j < n; ++j) {
|
||||
ut32 *val = malloc(sizeof(ut32));
|
||||
*val = j;
|
||||
rz_list_append(list, val);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_prepend(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[prepend] 10k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
for (int j = 0; j < 10000; j++) {
|
||||
rz_list_prepend(l, (void *)(intptr_t)j);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[prepend] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
rz_list_prepend(l, (void *)(intptr_t)j);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[prepend] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
for (int j = 0; j < 1000000; j++) {
|
||||
rz_list_prepend(l, (void *)(intptr_t)j);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
/**
|
||||
* v1: Uses rz_list_delete per node (original)
|
||||
* Has extra overhead: unlinks node, updates prev/next, decrements length
|
||||
*/
|
||||
static void purge_v1(RzList *list) {
|
||||
RzListIter *it = list->head;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
rz_list_delete(list, it);
|
||||
it = next;
|
||||
}
|
||||
list->length = 0;
|
||||
list->head = list->tail = NULL;
|
||||
}
|
||||
|
||||
static void bench_pop(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[pop] 1k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000);
|
||||
while (l->length) {
|
||||
rz_list_pop(l);
|
||||
/**
|
||||
* v2: Direct free loop (new)
|
||||
* Tight loop: calls free fn on val, then frees the iter node directly
|
||||
*/
|
||||
static void purge_v2(RzList *list) {
|
||||
RzListIter *it = list->head;
|
||||
RzListFree fn = list->free;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
if (fn && it->val) {
|
||||
fn(it->val);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[pop] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
while (l->length) {
|
||||
rz_list_pop(l);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[pop] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000000);
|
||||
while (l->length) {
|
||||
rz_list_pop(l);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
list->length = 0;
|
||||
list->head = list->tail = NULL;
|
||||
}
|
||||
|
||||
static void bench_pop_head(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[pop_head] 1k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000);
|
||||
while (l->length) {
|
||||
rz_list_pop_head(l);
|
||||
/**
|
||||
* v3: Hoisted free loop
|
||||
*/
|
||||
static void purge_v3(RzList *list) {
|
||||
RzListIter *it = list->head;
|
||||
RzListFree fn = list->free;
|
||||
if (fn) {
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
fn(it->val);
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[pop_head] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
while (l->length) {
|
||||
rz_list_pop_head(l);
|
||||
} else {
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[pop_head] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000000);
|
||||
while (l->length) {
|
||||
rz_list_pop_head(l);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
}
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->length = 0;
|
||||
}
|
||||
|
||||
static void bench_del_n(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[del_n@0] 1k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000);
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
rz_list_del_n(l, 0);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[del_n@0] 100k", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
rz_list_del_n(l, 0);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[del_n@0] 1m", i, t_out, 1, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000000);
|
||||
for (int j = 0; j < 1000000; j++) {
|
||||
rz_list_del_n(l, 0);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[del_n@tail] 100k", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
while (l->length) {
|
||||
rz_list_del_n(l, l->length - 1);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[del_n@mid] 10k", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 10000);
|
||||
while (l->length) {
|
||||
rz_list_del_n(l, l->length / 2);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
static void purge_v4_with_free_cb(RzList *list) {
|
||||
RzListIter *it = list->head;
|
||||
RzListFree fn = list->free;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
fn(it->val);
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_purge(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[purge] 10k", i, t_out, 1000, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 10000);
|
||||
rz_list_purge(l);
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[purge] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
rz_list_purge(l);
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[purge] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 1000000);
|
||||
rz_list_purge(l);
|
||||
rz_list_free(l);
|
||||
});
|
||||
static void purge_v4_no_free_cb(RzList *list) {
|
||||
RzListIter *it = list->head;
|
||||
while (it) {
|
||||
RzListIter *next = it->next;
|
||||
free(it);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_mixed_append_pop(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[mixed append+pop] 100k", i, t_out, 100, {
|
||||
RzList *l = rz_list_new();
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
rz_list_append(l, (void *)(intptr_t)j);
|
||||
if (j % 2 == 0)
|
||||
rz_list_pop(l);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[mixed append+pop] 1m", i, t_out, 10, {
|
||||
RzList *l = rz_list_new();
|
||||
for (int j = 0; j < 1000000; j++) {
|
||||
rz_list_append(l, (void *)(intptr_t)j);
|
||||
if (j % 2 == 0)
|
||||
rz_list_pop(l);
|
||||
}
|
||||
rz_list_free(l);
|
||||
});
|
||||
/**
|
||||
* v4: Direct free loop with hoisted free fn check (split into helpers)
|
||||
*/
|
||||
static void purge_v4(RzList *list) {
|
||||
if (list->free) {
|
||||
purge_v4_with_free_cb(list);
|
||||
} else {
|
||||
purge_v4_no_free_cb(list);
|
||||
}
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->length = 0;
|
||||
}
|
||||
|
||||
static void bench_mixed_purge_refill(RzTable *t_out) {
|
||||
RZ_BENCH_RUN_I("[mixed purge+refill] 10k", i, t_out, 500, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 10000);
|
||||
rz_list_purge(l);
|
||||
_fill(l, 10000);
|
||||
rz_list_free(l);
|
||||
});
|
||||
RZ_BENCH_RUN_I("[mixed purge+refill] 100k", i, t_out, 50, {
|
||||
RzList *l = rz_list_new();
|
||||
_fill(l, 100000);
|
||||
rz_list_purge(l);
|
||||
_fill(l, 100000);
|
||||
rz_list_free(l);
|
||||
// --- No free function (pointer-only lists) ---
|
||||
|
||||
static void bench_purge_v1_small_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v1_1k_no_free", t_out, 1000, {
|
||||
populate_list(list, 1000);
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
static void bench_purge_v2_small_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v2_1k_no_free", t_out, 1000, {
|
||||
populate_list(list, 1000);
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_small_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v3_1k_no_free", t_out, 1000, {
|
||||
populate_list(list, 1000);
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_small_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v4_1k_no_free", t_out, 1000, {
|
||||
populate_list(list, 1000);
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v1_medium_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v1_100k_no_free", t_out, 100, {
|
||||
populate_list(list, 100000);
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v2_medium_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v2_100k_no_free", t_out, 100, {
|
||||
populate_list(list, 100000);
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_medium_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v3_100k_no_free", t_out, 100, {
|
||||
populate_list(list, 100000);
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_medium_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v4_100k_no_free", t_out, 100, {
|
||||
populate_list(list, 100000);
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v1_large_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v1_1m_no_free", t_out, 10, {
|
||||
populate_list(list, 1000000);
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v2_large_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v2_1m_no_free", t_out, 10, {
|
||||
populate_list(list, 1000000);
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_large_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v3_1m_no_free", t_out, 10, {
|
||||
populate_list(list, 1000000);
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_large_no_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v4_1m_no_free", t_out, 10, {
|
||||
populate_list(list, 1000000);
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v1_medium_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v1_100k_with_free", t_out, 100, {
|
||||
populate_list_alloc(list, 100000);
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v2_medium_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v2_100k_with_free", t_out, 100, {
|
||||
populate_list_alloc(list, 100000);
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_medium_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v3_100k_with_free", t_out, 100, {
|
||||
populate_list_alloc(list, 100000);
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_medium_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v4_100k_with_free", t_out, 100, {
|
||||
populate_list_alloc(list, 100000);
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v1_large_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v1_1m_with_free", t_out, 10, {
|
||||
populate_list_alloc(list, 1000000);
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v2_large_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v2_1m_with_free", t_out, 10, {
|
||||
populate_list_alloc(list, 1000000);
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_large_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v3_1m_with_free", t_out, 10, {
|
||||
populate_list_alloc(list, 1000000);
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_large_with_free(RzTable *t_out) {
|
||||
RzList *list = rz_list_newf(free);
|
||||
RZ_BENCH_RUN("purge_v4_1m_with_free", t_out, 10, {
|
||||
populate_list_alloc(list, 1000000);
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
// --- Empty list (base overhead) ---
|
||||
|
||||
static void bench_purge_v1_empty(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v1_empty", t_out, 10000, {
|
||||
purge_v1(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v2_empty(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v2_empty", t_out, 10000, {
|
||||
purge_v2(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v3_empty(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v3_empty", t_out, 10000, {
|
||||
purge_v3(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
static void bench_purge_v4_empty(RzTable *t_out) {
|
||||
RzList *list = rz_list_new();
|
||||
RZ_BENCH_RUN("purge_v4_empty", t_out, 10000, {
|
||||
purge_v4(list);
|
||||
});
|
||||
rz_list_free(list);
|
||||
}
|
||||
|
||||
int main() {
|
||||
RzTable *t = rz_table_new();
|
||||
RZ_BENCH_TABLE_INIT(t);
|
||||
|
||||
bench_append(t);
|
||||
bench_prepend(t);
|
||||
bench_pop(t);
|
||||
bench_pop_head(t);
|
||||
bench_del_n(t);
|
||||
bench_purge(t);
|
||||
bench_mixed_append_pop(t);
|
||||
bench_mixed_purge_refill(t);
|
||||
// Empty list - base overhead
|
||||
bench_purge_v1_empty(t);
|
||||
bench_purge_v2_empty(t);
|
||||
bench_purge_v3_empty(t);
|
||||
bench_purge_v4_empty(t);
|
||||
|
||||
// No free function - pointer-only lists
|
||||
bench_purge_v1_small_no_free(t);
|
||||
bench_purge_v2_small_no_free(t);
|
||||
bench_purge_v3_small_no_free(t);
|
||||
bench_purge_v4_small_no_free(t);
|
||||
|
||||
bench_purge_v1_medium_no_free(t);
|
||||
bench_purge_v2_medium_no_free(t);
|
||||
bench_purge_v3_medium_no_free(t);
|
||||
bench_purge_v4_medium_no_free(t);
|
||||
|
||||
bench_purge_v1_large_no_free(t);
|
||||
bench_purge_v2_large_no_free(t);
|
||||
bench_purge_v3_large_no_free(t);
|
||||
bench_purge_v4_large_no_free(t);
|
||||
|
||||
// With free function - heap-allocated elements
|
||||
bench_purge_v1_medium_with_free(t);
|
||||
bench_purge_v2_medium_with_free(t);
|
||||
bench_purge_v3_medium_with_free(t);
|
||||
bench_purge_v4_medium_with_free(t);
|
||||
|
||||
bench_purge_v1_large_with_free(t);
|
||||
bench_purge_v2_large_with_free(t);
|
||||
bench_purge_v3_large_with_free(t);
|
||||
bench_purge_v4_large_with_free(t);
|
||||
|
||||
RZ_BENCH_TABLE_PRINT_AND_FREE(t);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,6 @@
|
|||
#include "minunit.h"
|
||||
#define BUF_LENGTH 100
|
||||
|
||||
static void dummy_free(void *ptr) {
|
||||
// This is just to trigger the code path
|
||||
}
|
||||
|
||||
bool test_rz_list_size(void) {
|
||||
// Test that rz_list adding and deleting works correctly.
|
||||
int i;
|
||||
|
|
@ -55,7 +51,7 @@ bool test_rz_list_join(void) {
|
|||
rz_list_append(list2, (void *)test2);
|
||||
int joined = rz_list_join(list1, list2);
|
||||
mu_assert_eq(joined, 1, "rz_list_join of two lists");
|
||||
mu_assert_eq(rz_list_length(list1), 2, "rz_list_join two single element lists result length is 2");
|
||||
mu_assert_eq(rz_list_length(list1), 2, "rz_list_join two single element lists result length is 1");
|
||||
rz_list_free(list1);
|
||||
rz_list_free(list2);
|
||||
mu_end;
|
||||
|
|
@ -525,38 +521,6 @@ bool test_rz_list_sorted_uniq() {
|
|||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_list_heavy_growth(void) {
|
||||
RzList *list = rz_list_new();
|
||||
const int count = 2000;
|
||||
for (int i = 0; i < count; i++) {
|
||||
mu_assert_notnull(rz_list_append(list, (void *)(intptr_t)i), "heavy append failed");
|
||||
}
|
||||
mu_assert_eq(rz_list_length(list), count, "length mismatch");
|
||||
rz_list_free(list);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_list_insert_head(void) {
|
||||
RzList *list = rz_list_new();
|
||||
rz_list_append(list, (void *)0xAAA);
|
||||
RzListIter *it = rz_list_insert(list, 0, (void *)0xBBB);
|
||||
mu_assert_notnull(it, "insert failed");
|
||||
mu_assert_ptreq(list->head, it, "list->head was not updated correctly");
|
||||
mu_assert_ptreq(it->next->val, (void *)0xAAA, "original head not shifted");
|
||||
rz_list_free(list);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_list_set_n_free(void) {
|
||||
RzList *list = rz_list_newf(dummy_free);
|
||||
rz_list_append(list, (void *)0x111);
|
||||
rz_list_append(list, (void *)0x222);
|
||||
mu_assert_eq(rz_list_set_n(list, 1, (void *)0x333), true, "set_n failed");
|
||||
mu_assert_ptreq(rz_list_get_n(list, 1), (void *)0x333, "val not updated");
|
||||
rz_list_free(list);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
int all_tests() {
|
||||
mu_run_test(test_rz_list_size);
|
||||
mu_run_test(test_rz_list_values);
|
||||
|
|
@ -577,9 +541,6 @@ int all_tests() {
|
|||
mu_run_test(test_rz_list_find_val);
|
||||
mu_run_test(test_rz_list_from_iter);
|
||||
mu_run_test(test_rz_list_sorted_uniq);
|
||||
mu_run_test(test_rz_list_heavy_growth);
|
||||
mu_run_test(test_rz_list_insert_head);
|
||||
mu_run_test(test_rz_list_set_n_free);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue