mirror of
https://github.com/redis/redis.git
synced 2026-06-08 16:24:26 -04:00
Introduce internal append-only pointer vector DS (#15039)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run
Refactoring work for follow-ups (e.g. subkey notifications #14958), splitting reusable infrastructure from feature logic. Optimized for stack allocation with optional growth to heap. Usage: Start on stack (grow to heap): vec v; void *vstack[8]; vecInit(&v, vstack, 8); Start embedded (grow to heap): typedef struct { vec v; void *vembedded[8]; } obj; vecInit(&obj.v, obj.vembedded, 8); Heap only (capacity 8 or 0): vecInit(&v, NULL, 8); vecInit(&v, NULL, 0); Reserve based on size: vecInit(&v, vstack, 8); vecReserve(&v, varsize); // <=8 uses stack, else heap
This commit is contained in:
parent
2049c7fe32
commit
3f810d35bf
4 changed files with 268 additions and 1 deletions
|
|
@ -382,7 +382,7 @@ endif
|
|||
|
||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o
|
||||
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o
|
||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||
|
|
|
|||
|
|
@ -7797,6 +7797,7 @@ int __test_num = 0;
|
|||
typedef int redisTestProc(int argc, char **argv, int flags);
|
||||
int bitopsTest(int argc, char **argv, int flags);
|
||||
int zsetTest(int argc, char **argv, int flags);
|
||||
int vectorTest(int argc, char **argv, int flags);
|
||||
struct redisTest {
|
||||
char *name;
|
||||
redisTestProc *proc;
|
||||
|
|
@ -7820,6 +7821,7 @@ struct redisTest {
|
|||
{"fwtree", fwtreeTest},
|
||||
{"estore", estoreTest},
|
||||
{"ebuckets", ebucketsTest},
|
||||
{"vector", vectorTest},
|
||||
{"bitmap", bitopsTest},
|
||||
{"rax", raxTest},
|
||||
{"zset", zsetTest},
|
||||
|
|
|
|||
173
src/vector.c
Normal file
173
src/vector.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/* vector.c - Simple append-only vector implementation
|
||||
*
|
||||
* Copyright (c) 2026-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of (a) the Redis Source Available License 2.0
|
||||
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
|
||||
* GNU Affero General Public License v3 (AGPLv3).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "vector.h"
|
||||
#include "redisassert.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define VEC_DEFAULT_INITCAP 8
|
||||
|
||||
/*
|
||||
* Vector initialization.
|
||||
*
|
||||
* Modes:
|
||||
* - stack != NULL: use caller-provided storage for the first initcap items.
|
||||
* - stack == NULL && initcap > 0: start heap-backed with an initial 'initcap' capacity.
|
||||
* - stack == NULL && initcap == 0: start heap-backed with no initial storage.
|
||||
*/
|
||||
void vecInit(vec *v, void **stack, size_t initcap) {
|
||||
/* If stack is provided, initcap must be > 0 and at the size of the stack */
|
||||
assert(initcap > 0 || stack == NULL);
|
||||
|
||||
v->size = 0;
|
||||
v->cap = initcap;
|
||||
v->stack = stack; /* stack is NULL if not used */
|
||||
|
||||
/* now init data either stack, heap or NULL */
|
||||
v->data = (stack) ? stack : ((initcap > 0) ? zmalloc(initcap * sizeof(void *)) : NULL);
|
||||
}
|
||||
|
||||
/* Free only heap storage if any */
|
||||
void vecRelease(vec *v) {
|
||||
/* if data is not stack-allocated and is not NULL, free it */
|
||||
if (v->data && v->data != v->stack)
|
||||
zfree(v->data);
|
||||
v->size = 0;
|
||||
v->cap = 0;
|
||||
v->data = NULL;
|
||||
v->stack = NULL;
|
||||
}
|
||||
|
||||
/* Reset the logical length to zero while preserving allocated storage. */
|
||||
void vecClear(vec *v) {
|
||||
v->size = 0;
|
||||
}
|
||||
|
||||
/* Return the number of elements in the vector. */
|
||||
size_t vecSize(const vec *v) {
|
||||
return v->size;
|
||||
}
|
||||
|
||||
/* Get element at index. index must be < vecSize(v). */
|
||||
void *vecGet(const vec *v, size_t index) {
|
||||
assert(index < v->size);
|
||||
return v->data[index];
|
||||
}
|
||||
|
||||
/* Return the contiguous backing array. */
|
||||
void **vecData(vec *v) {
|
||||
return v->data;
|
||||
}
|
||||
|
||||
/* Ensure capacity is at least mincap. */
|
||||
void vecReserve(vec *v, size_t mincap) {
|
||||
void **newdata;
|
||||
|
||||
if (mincap <= v->cap) return;
|
||||
|
||||
/* If no heap storage is used yet, allocate and copy from stack if needed. */
|
||||
if (v->data == v->stack) {
|
||||
newdata = zmalloc(mincap * sizeof(void *));
|
||||
if (v->size) memcpy(newdata, v->data, v->size * sizeof(void *));
|
||||
} else {
|
||||
newdata = zrealloc(v->data, mincap * sizeof(void *));
|
||||
}
|
||||
|
||||
v->data = newdata;
|
||||
v->cap = mincap;
|
||||
}
|
||||
|
||||
/* Append one element, growing storage as needed. */
|
||||
void vecPush(vec *v, void *value) {
|
||||
if (v->size == v->cap) {
|
||||
size_t newcap = (v->cap > 0) ? v->cap * 2 : VEC_DEFAULT_INITCAP;
|
||||
vecReserve(v, newcap);
|
||||
}
|
||||
|
||||
v->data[v->size++] = value;
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "testhelp.h"
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
int vectorTest(int argc, char **argv, int flags)
|
||||
{
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(flags);
|
||||
|
||||
vec v;
|
||||
void *vstack[2];
|
||||
int one = 1, two = 2, three = 3, four = 4, five = 5, six = 6;
|
||||
|
||||
vecInit(&v, vstack, 2);
|
||||
test_cond("vecInit() stack-backed size is 0", vecSize(&v) == 0);
|
||||
test_cond("vecInit() uses stack buffer", vecData(&v) == vstack);
|
||||
vecReserve(&v, 1);
|
||||
test_cond("vecReserve() no-ops when capacity is already sufficient",
|
||||
v.cap == 2 && vecData(&v) == vstack);
|
||||
vecPush(&v, &one);
|
||||
vecPush(&v, &two);
|
||||
test_cond("vecPush() appends into stack storage",
|
||||
vecSize(&v) == 2 && vecData(&v) == vstack &&
|
||||
vecGet(&v, 0) == &one && vecGet(&v, 1) == &two);
|
||||
vecReserve(&v, 4);
|
||||
test_cond("vecReserve() spills from stack to heap preserving values",
|
||||
v.cap == 4 && vecData(&v) != vstack &&
|
||||
vecGet(&v, 0) == &one && vecGet(&v, 1) == &two);
|
||||
vecPush(&v, &three);
|
||||
test_cond("vecPush() spills from stack to heap preserving values",
|
||||
vecSize(&v) == 3 &&
|
||||
vecData(&v) != vstack && vecGet(&v, 0) == &one &&
|
||||
vecGet(&v, 1) == &two && vecGet(&v, 2) == &three);
|
||||
|
||||
void **heap_data = vecData(&v);
|
||||
vecClear(&v);
|
||||
test_cond("vecClear() resets size but preserves storage",
|
||||
vecSize(&v) == 0 && vecData(&v) == heap_data);
|
||||
vecRelease(&v);
|
||||
test_cond("vecRelease() resets vector state",
|
||||
vecSize(&v) == 0 && vecData(&v) == NULL && v.cap == 0);
|
||||
|
||||
vecInit(&v, NULL, 4);
|
||||
test_cond("vecInit() heap-backed hint allocates storage",
|
||||
vecSize(&v) == 0 && vecData(&v) != NULL && v.cap == 4);
|
||||
vecPush(&v, &four);
|
||||
test_cond("vecPush() works in heap-backed mode",
|
||||
vecGet(&v, 0) == &four);
|
||||
vecReserve(&v, 8);
|
||||
test_cond("vecReserve() grows heap-backed storage preserving values",
|
||||
v.cap == 8 && vecGet(&v, 0) == &four);
|
||||
vecRelease(&v);
|
||||
|
||||
vecInit(&v, NULL, 0);
|
||||
vecReserve(&v, 6);
|
||||
test_cond("vecReserve() allocates heap storage from empty vector",
|
||||
v.cap == 6 && vecData(&v) != NULL);
|
||||
vecPush(&v, &five);
|
||||
vecPush(&v, &six);
|
||||
test_cond("vecPush() works after vecReserve() on empty vector",
|
||||
vecSize(&v) == 2 &&
|
||||
vecGet(&v, 0) == &five && vecGet(&v, 1) == &six);
|
||||
vecRelease(&v);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
92
src/vector.h
Normal file
92
src/vector.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef REDIS_VECTOR_H
|
||||
#define REDIS_VECTOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* Simple append-only vector (dynamic array) of void * elements.
|
||||
*
|
||||
* Design:
|
||||
* --------
|
||||
* - Stores elements in a contiguous array (void **).
|
||||
* - Supports append (vecPush) and read access.
|
||||
* - Optionally uses caller-provided stack buffer to avoid heap allocations.
|
||||
* - See also comment in vector.c of vecInit() for more details.
|
||||
*
|
||||
* Memory:
|
||||
* -------
|
||||
* - vecRelease() frees heap memory if used.
|
||||
* - Stack buffer is never freed.
|
||||
* - Stored elements are never freed.
|
||||
*
|
||||
* Modes:
|
||||
* -------
|
||||
* 1. Start On Stack (grow to heap): vec v;
|
||||
* void *vstack[8];
|
||||
* ...
|
||||
* vecInit(&v, vstack, 8);
|
||||
*
|
||||
* Start Embedded (grow to heap): typedef struct {
|
||||
* vec v;
|
||||
* void *vembedded[8];
|
||||
* } obj;
|
||||
* ...
|
||||
* vecInit(&obj->v, obj->vembedded, 8);
|
||||
*
|
||||
* 2. Heap only, init capacity 8: vec v;
|
||||
* ...
|
||||
* vecInit(&v, NULL, 8);
|
||||
*
|
||||
* Heap only, init capacity 0: vec v;
|
||||
* ...
|
||||
* vecInit(&v, NULL, 0);
|
||||
*
|
||||
* 3. Depends on var size: vec v;
|
||||
* void *vstack[8];
|
||||
* vecInit(&v, vstack, 8);
|
||||
* vecReserve(&v, varsize); // varsize <= 8 ? stack : heap
|
||||
*
|
||||
* Notes:
|
||||
* ------
|
||||
* - Not thread-safe.
|
||||
* - If stack == NULL and initcap > 0, initcap is treated as an initial
|
||||
* heap-capacity hint.
|
||||
* - When used in Redis core, the implementation should use the Redis allocator
|
||||
* wrappers (zmalloc / zrealloc / zfree) rather than libc allocation APIs.
|
||||
*/
|
||||
|
||||
typedef struct vec {
|
||||
size_t size; /* Number of elements in the vector. */
|
||||
size_t cap; /* Capacity of the vector. */
|
||||
void **data; /* Heap-allocated storage or refers to stack. */
|
||||
void **stack; /* Optional stack buffer. */
|
||||
} vec;
|
||||
|
||||
/* Initialize a vector */
|
||||
void vecInit(vec *v, void **stack, size_t initcap);
|
||||
|
||||
/* Free only heap storage if any */
|
||||
void vecRelease(vec *v);
|
||||
|
||||
/* Reset the logical length to zero while preserving allocated storage. */
|
||||
void vecClear(vec *v);
|
||||
|
||||
size_t vecSize(const vec *v);
|
||||
|
||||
/* Requires index < vecSize(v). */
|
||||
void *vecGet(const vec *v, size_t index);
|
||||
|
||||
/* Return the contiguous backing array. */
|
||||
void **vecData(vec *v);
|
||||
|
||||
/* Ensure capacity is at least mincap. */
|
||||
void vecReserve(vec *v, size_t mincap);
|
||||
|
||||
/* Append one element, growing storage as needed. */
|
||||
void vecPush(vec *v, void *value);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int vectorTest(int argc, char **argv, int flags);
|
||||
#endif
|
||||
|
||||
#endif /* REDIS_VECTOR_H */
|
||||
Loading…
Reference in a new issue