uint-spbset: reused empty chunks

Fix chunk allocations by reusing existing empty chunks when a new offset
is needed. Before this fix, spbsets would only ever grow with added
numbers outside the range of existing chunks.

Closes #22340
This commit is contained in:
Stefan Eissing 2026-07-17 14:10:03 +02:00 committed by Daniel Stenberg
parent 5bb7d7a4f2
commit c5fd5eb55a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -74,90 +74,142 @@ uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset)
return n;
}
static bool uint32_spbset_empty_chunk(struct uint32_spbset_chunk *chunk)
{
uint32_t i;
for(i = 0; i < CURL_UINT32_SPBSET_CH_SLOTS; ++i) {
if(chunk->slots[i])
return FALSE;
}
return TRUE;
}
static struct uint32_spbset_chunk *uint32_spbset_unlink_empty(
struct uint32_spbset *bset, uint32_t for_offset)
{
struct uint32_spbset_chunk *chunk, **panchor = NULL;
for(chunk = &bset->head; chunk;
panchor = &chunk->next, chunk = chunk->next) {
if(uint32_spbset_empty_chunk(chunk))
break;
}
if(chunk) {
if(chunk == &bset->head) { /* head chunk is empty */
if(!bset->head.next || (for_offset < bset->head.next->offset)) {
return &bset->head;
}
/* swap head and next, unlink */
chunk = bset->head.next;
memcpy(&bset->head, chunk, sizeof(bset->head));
memset(chunk, 0, sizeof(*chunk));
}
else {
*panchor = chunk->next; /* unlink */
memset(chunk, 0, sizeof(*chunk));
}
}
return chunk;
}
static struct uint32_spbset_chunk *uint32_spbset_insert_chunk(
struct uint32_spbset *bset, struct uint32_spbset_chunk *nchunk)
{
struct uint32_spbset_chunk *chunk, **panchor;
/* insert nchunk into set's ordered chunk list */
if(nchunk->offset < bset->head.offset) {
/* swap chunk and head */
uint32_t offset = nchunk->offset;
memcpy(nchunk, &bset->head, sizeof(*nchunk));
memset(&bset->head, 0, sizeof(bset->head));
bset->head.next = nchunk;
bset->head.offset = offset;
return &bset->head;
}
DEBUGASSERT(nchunk->offset > bset->head.offset);
panchor = &bset->head.next;
for(chunk = *panchor; chunk;
panchor = &chunk->next, chunk = chunk->next) {
if(chunk->offset > nchunk->offset) { /* insert before this chunk */
nchunk->next = chunk;
*panchor = nchunk;
return nchunk;
}
}
/* no chunk with larger offset, append */
*panchor = nchunk;
return nchunk;
}
static struct uint32_spbset_chunk *uint32_spbset_get_chunk(
struct uint32_spbset *bset, uint32_t i, bool grow)
{
struct uint32_spbset_chunk *chunk, **panchor = NULL;
struct uint32_spbset_chunk *chunk;
uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK);
if(!bset)
return NULL;
for(chunk = &bset->head; chunk;
panchor = &chunk->next, chunk = chunk->next) {
if(chunk->offset == i_offset) {
for(chunk = &bset->head; chunk; chunk = chunk->next) {
if(chunk->offset == i_offset)
return chunk;
}
else if(chunk->offset > i_offset) {
/* need new chunk here */
chunk = NULL;
break;
}
else if(chunk->offset > i_offset)
break; /* need new chunk here */
}
if(!grow)
if(!grow) /* just a check if the chunk exists */
return NULL;
/* need a new one */
chunk = curlx_calloc(1, sizeof(*chunk));
if(!chunk)
return NULL;
/* Is there an empty chunk to reuse? */
chunk = uint32_spbset_unlink_empty(bset, i_offset);
if(chunk) {
chunk->offset = i_offset;
if(chunk == &bset->head) /* head chunk is empty, stayed linked */
return &bset->head;
/* was really unlinked, need to insert below */
}
else {
/* need a new one */
chunk = curlx_calloc(1, sizeof(*chunk));
if(!chunk)
return NULL;
chunk->offset = i_offset;
}
if(panchor) { /* insert between panchor and *panchor */
chunk->next = *panchor;
*panchor = chunk;
}
else { /* prepend to head, switching places */
memcpy(chunk, &bset->head, sizeof(*chunk));
memset(&bset->head, 0, sizeof(bset->head));
bset->head.next = chunk;
}
chunk->offset = i_offset;
return chunk;
return uint32_spbset_insert_chunk(bset, chunk);
}
bool Curl_uint32_spbset_add(struct uint32_spbset *bset, uint32_t i)
{
struct uint32_spbset_chunk *chunk;
uint32_t i_chunk;
chunk = uint32_spbset_get_chunk(bset, i, TRUE);
struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, TRUE);
if(!chunk)
return FALSE;
DEBUGASSERT(i >= chunk->offset);
i_chunk = (i - chunk->offset);
DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
chunk->slots[(i_chunk / 64)] |= ((uint64_t)1 << (i_chunk % 64));
i -= chunk->offset;
DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
chunk->slots[(i / 64)] |= ((uint64_t)1 << (i % 64));
return TRUE;
}
void Curl_uint32_spbset_remove(struct uint32_spbset *bset, uint32_t i)
{
struct uint32_spbset_chunk *chunk;
uint32_t i_chunk;
chunk = uint32_spbset_get_chunk(bset, i, FALSE);
struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE);
if(chunk) {
DEBUGASSERT(i >= chunk->offset);
i_chunk = (i - chunk->offset);
DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
chunk->slots[(i_chunk / 64)] &= ~((uint64_t)1 << (i_chunk % 64));
i -= chunk->offset;
DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
chunk->slots[(i / 64)] &= ~((uint64_t)1 << (i % 64));
}
}
bool Curl_uint32_spbset_contains(struct uint32_spbset *bset, uint32_t i)
{
struct uint32_spbset_chunk *chunk;
uint32_t i_chunk;
chunk = uint32_spbset_get_chunk(bset, i, FALSE);
struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE);
if(chunk) {
DEBUGASSERT(i >= chunk->offset);
i_chunk = (i - chunk->offset);
DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
return (chunk->slots[i_chunk / 64] &
((uint64_t)1 << (i_chunk % 64))) != 0;
i -= chunk->offset;
DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
return (chunk->slots[i / 64] & ((uint64_t)1 << (i % 64))) != 0;
}
return FALSE;
}