bird/lib/mempool.c
Maria Matejka f772afc525 Memory statistics split into Effective and Overhead
This feature is intended mostly for checking that BIRD's allocation
strategies don't consume much memory space. There are some cases where
withdrawing routes in a specific order lead to memory fragmentation and
this output should give the user at least a notion of how much memory is
actually used for data storage and how much memory is "just allocated"
or used for overhead.

Also raising the "system allocator overhead estimation" from 8 to 16
bytes; it is probably even more. I've found 16 as a local minimum in
best scenarios among reachable machines. I couldn't find any reasonable
method to estimate this value when BIRD starts up.

This commit also fixes the inaccurate computation of memory overhead for
slabs where the "system allocater overhead estimation" was improperly
added to the size of mmap-ed memory.
2021-11-27 22:54:15 +01:00

324 lines
7.2 KiB
C

/*
* BIRD Resource Manager -- Memory Pools
*
* (c) 1998--2000 Martin Mares <mj@ucw.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
/**
* DOC: Linear memory pools
*
* Linear memory pools are collections of memory blocks which
* support very fast allocation of new blocks, but are able to free only
* the whole collection at once (or in stack order).
*
* Example: Each configuration is described by a complex system of structures,
* linked lists and function trees which are all allocated from a single linear
* pool, thus they can be freed at once when the configuration is no longer used.
*/
#include <stdlib.h>
#include <stdint.h>
#include "nest/bird.h"
#include "lib/resource.h"
#include "lib/string.h"
struct lp_chunk {
struct lp_chunk *next;
uint size;
uintptr_t data_align[0];
byte data[0];
};
const int lp_chunk_size = sizeof(struct lp_chunk);
struct linpool {
resource r;
byte *ptr, *end;
struct lp_chunk *first, *current; /* Normal (reusable) chunks */
struct lp_chunk *first_large; /* Large chunks */
uint chunk_size, threshold, total, total_large;
};
static void lp_free(resource *);
static void lp_dump(resource *);
static resource *lp_lookup(resource *, unsigned long);
static struct resmem lp_memsize(resource *r);
static struct resclass lp_class = {
"LinPool",
sizeof(struct linpool),
lp_free,
lp_dump,
lp_lookup,
lp_memsize
};
/**
* lp_new - create a new linear memory pool
* @p: pool
* @blk: block size
*
* lp_new() creates a new linear memory pool resource inside the pool @p.
* The linear pool consists of a list of memory chunks of size at least
* @blk.
*/
linpool
*lp_new(pool *p, uint blk)
{
linpool *m = ralloc(p, &lp_class);
m->chunk_size = blk;
m->threshold = 3*blk/4;
return m;
}
/**
* lp_alloc - allocate memory from a &linpool
* @m: linear memory pool
* @size: amount of memory
*
* lp_alloc() allocates @size bytes of memory from a &linpool @m
* and it returns a pointer to the allocated memory.
*
* It works by trying to find free space in the last memory chunk
* associated with the &linpool and creating a new chunk of the standard
* size (as specified during lp_new()) if the free space is too small
* to satisfy the allocation. If @size is too large to fit in a standard
* size chunk, an "overflow" chunk is created for it instead.
*/
void *
lp_alloc(linpool *m, uint size)
{
byte *a = (byte *) BIRD_ALIGN((unsigned long) m->ptr, CPU_STRUCT_ALIGN);
byte *e = a + size;
if (e <= m->end)
{
m->ptr = e;
return a;
}
else
{
struct lp_chunk *c;
if (size >= m->threshold)
{
/* Too large => allocate large chunk */
c = xmalloc(sizeof(struct lp_chunk) + size);
m->total_large += size;
c->next = m->first_large;
m->first_large = c;
c->size = size;
}
else
{
if (m->current && m->current->next)
{
/* Still have free chunks from previous incarnation (before lp_flush()) */
c = m->current->next;
}
else
{
/* Need to allocate a new chunk */
c = xmalloc(sizeof(struct lp_chunk) + m->chunk_size);
m->total += m->chunk_size;
c->next = NULL;
c->size = m->chunk_size;
if (m->current)
m->current->next = c;
else
m->first = c;
}
m->current = c;
m->ptr = c->data + size;
m->end = c->data + m->chunk_size;
}
return c->data;
}
}
/**
* lp_allocu - allocate unaligned memory from a &linpool
* @m: linear memory pool
* @size: amount of memory
*
* lp_allocu() allocates @size bytes of memory from a &linpool @m
* and it returns a pointer to the allocated memory. It doesn't
* attempt to align the memory block, giving a very efficient way
* how to allocate strings without any space overhead.
*/
void *
lp_allocu(linpool *m, uint size)
{
byte *a = m->ptr;
byte *e = a + size;
if (e <= m->end)
{
m->ptr = e;
return a;
}
return lp_alloc(m, size);
}
/**
* lp_allocz - allocate cleared memory from a &linpool
* @m: linear memory pool
* @size: amount of memory
*
* This function is identical to lp_alloc() except that it
* clears the allocated memory block.
*/
void *
lp_allocz(linpool *m, uint size)
{
void *z = lp_alloc(m, size);
bzero(z, size);
return z;
}
/**
* lp_flush - flush a linear memory pool
* @m: linear memory pool
*
* This function frees the whole contents of the given &linpool @m,
* but leaves the pool itself.
*/
void
lp_flush(linpool *m)
{
struct lp_chunk *c;
/* Move ptr to the first chunk and free all large chunks */
m->current = c = m->first;
m->ptr = c ? c->data : NULL;
m->end = c ? c->data + m->chunk_size : NULL;
while (c = m->first_large)
{
m->first_large = c->next;
xfree(c);
}
m->total_large = 0;
}
/**
* lp_save - save the state of a linear memory pool
* @m: linear memory pool
* @p: state buffer
*
* This function saves the state of a linear memory pool. Saved state can be
* used later to restore the pool (to free memory allocated since).
*/
void
lp_save(linpool *m, lp_state *p)
{
p->current = m->current;
p->large = m->first_large;
p->ptr = m->ptr;
}
/**
* lp_restore - restore the state of a linear memory pool
* @m: linear memory pool
* @p: saved state
*
* This function restores the state of a linear memory pool, freeing all memory
* allocated since the state was saved. Note that the function cannot un-free
* the memory, therefore the function also invalidates other states that were
* saved between (on the same pool).
*/
void
lp_restore(linpool *m, lp_state *p)
{
struct lp_chunk *c;
/* Move ptr to the saved pos and free all newer large chunks */
m->current = c = p->current;
m->ptr = p->ptr;
m->end = c ? c->data + m->chunk_size : NULL;
while ((c = m->first_large) && (c != p->large))
{
m->first_large = c->next;
m->total_large -= c->size;
xfree(c);
}
}
static void
lp_free(resource *r)
{
linpool *m = (linpool *) r;
struct lp_chunk *c, *d;
for(d=m->first; d; d = c)
{
c = d->next;
xfree(d);
}
for(d=m->first_large; d; d = c)
{
c = d->next;
xfree(d);
}
}
static void
lp_dump(resource *r)
{
linpool *m = (linpool *) r;
struct lp_chunk *c;
int cnt, cntl;
for(cnt=0, c=m->first; c; c=c->next, cnt++)
;
for(cntl=0, c=m->first_large; c; c=c->next, cntl++)
;
debug("(chunk=%d threshold=%d count=%d+%d total=%d+%d)\n",
m->chunk_size,
m->threshold,
cnt,
cntl,
m->total,
m->total_large);
}
static struct resmem
lp_memsize(resource *r)
{
linpool *m = (linpool *) r;
struct lp_chunk *c;
int cnt = 0;
for(c=m->first; c; c=c->next)
cnt++;
for(c=m->first_large; c; c=c->next)
cnt++;
return (struct resmem) {
.effective = m->total + m->total_large,
.overhead = ALLOC_OVERHEAD + sizeof(struct linpool) +
cnt * (ALLOC_OVERHEAD + sizeof(struct lp_chunk)),
};
}
static resource *
lp_lookup(resource *r, unsigned long a)
{
linpool *m = (linpool *) r;
struct lp_chunk *c;
for(c=m->first; c; c=c->next)
if ((unsigned long) c->data <= a && (unsigned long) c->data + c->size > a)
return r;
for(c=m->first_large; c; c=c->next)
if ((unsigned long) c->data <= a && (unsigned long) c->data + c->size > a)
return r;
return NULL;
}