#include "Python.h" | |
#include "pyarena.h" | |
/* A simple arena block structure. | |
Measurements with standard library modules suggest the average | |
allocation is about 20 bytes and that most compiles use a single | |
block. | |
TODO(jhylton): Think about a realloc API, maybe just for the last | |
allocation? | |
*/ | |
#define DEFAULT_BLOCK_SIZE 8192 | |
#define ALIGNMENT 8 | |
#define ALIGNMENT_MASK (ALIGNMENT - 1) | |
#define ROUNDUP(x) (((x) + ALIGNMENT_MASK) & ~ALIGNMENT_MASK) | |
typedef struct _block { | |
/* Total number of bytes owned by this block available to pass out. | |
* Read-only after initialization. The first such byte starts at | |
* ab_mem. | |
*/ | |
size_t ab_size; | |
/* Total number of bytes already passed out. The next byte available | |
* to pass out starts at ab_mem + ab_offset. | |
*/ | |
size_t ab_offset; | |
/* An arena maintains a singly-linked, NULL-terminated list of | |
* all blocks owned by the arena. These are linked via the | |
* ab_next member. | |
*/ | |
struct _block *ab_next; | |
/* Pointer to the first allocatable byte owned by this block. Read- | |
* only after initialization. | |
*/ | |
void *ab_mem; | |
} block; | |
/* The arena manages two kinds of memory, blocks of raw memory | |
and a list of PyObject* pointers. PyObjects are decrefed | |
when the arena is freed. | |
*/ | |
struct _arena { | |
/* Pointer to the first block allocated for the arena, never NULL. | |
It is used only to find the first block when the arena is | |
being freed. | |
*/ | |
block *a_head; | |
/* Pointer to the block currently used for allocation. It's | |
ab_next field should be NULL. If it is not-null after a | |
call to block_alloc(), it means a new block has been allocated | |
and a_cur should be reset to point it. | |
*/ | |
block *a_cur; | |
/* A Python list object containing references to all the PyObject | |
pointers associated with this area. They will be DECREFed | |
when the arena is freed. | |
*/ | |
PyObject *a_objects; | |
#if defined(Py_DEBUG) | |
/* Debug output */ | |
size_t total_allocs; | |
size_t total_size; | |
size_t total_blocks; | |
size_t total_block_size; | |
size_t total_big_blocks; | |
#endif | |
}; | |
static block * | |
block_new(size_t size) | |
{ | |
/* Allocate header and block as one unit. | |
ab_mem points just past header. */ | |
block *b = (block *)malloc(sizeof(block) + size); | |
if (!b) | |
return NULL; | |
b->ab_size = size; | |
b->ab_mem = (void *)(b + 1); | |
b->ab_next = NULL; | |
b->ab_offset = ROUNDUP((Py_uintptr_t)(b->ab_mem)) - | |
(Py_uintptr_t)(b->ab_mem); | |
return b; | |
} | |
static void | |
block_free(block *b) { | |
while (b) { | |
block *next = b->ab_next; | |
free(b); | |
b = next; | |
} | |
} | |
static void * | |
block_alloc(block *b, size_t size) | |
{ | |
void *p; | |
assert(b); | |
size = ROUNDUP(size); | |
if (b->ab_offset + size > b->ab_size) { | |
/* If we need to allocate more memory than will fit in | |
the default block, allocate a one-off block that is | |
exactly the right size. */ | |
/* TODO(jhylton): Think about space waste at end of block */ | |
block *newbl = block_new( | |
size < DEFAULT_BLOCK_SIZE ? | |
DEFAULT_BLOCK_SIZE : size); | |
if (!newbl) | |
return NULL; | |
assert(!b->ab_next); | |
b->ab_next = newbl; | |
b = newbl; | |
} | |
assert(b->ab_offset + size <= b->ab_size); | |
p = (void *)(((char *)b->ab_mem) + b->ab_offset); | |
b->ab_offset += size; | |
return p; | |
} | |
PyArena * | |
PyArena_New() | |
{ | |
PyArena* arena = (PyArena *)malloc(sizeof(PyArena)); | |
if (!arena) | |
return (PyArena*)PyErr_NoMemory(); | |
arena->a_head = block_new(DEFAULT_BLOCK_SIZE); | |
arena->a_cur = arena->a_head; | |
if (!arena->a_head) { | |
free((void *)arena); | |
return (PyArena*)PyErr_NoMemory(); | |
} | |
arena->a_objects = PyList_New(0); | |
if (!arena->a_objects) { | |
block_free(arena->a_head); | |
free((void *)arena); | |
return (PyArena*)PyErr_NoMemory(); | |
} | |
#if defined(Py_DEBUG) | |
arena->total_allocs = 0; | |
arena->total_size = 0; | |
arena->total_blocks = 1; | |
arena->total_block_size = DEFAULT_BLOCK_SIZE; | |
arena->total_big_blocks = 0; | |
#endif | |
return arena; | |
} | |
void | |
PyArena_Free(PyArena *arena) | |
{ | |
int r; | |
assert(arena); | |
#if defined(Py_DEBUG) | |
/* | |
fprintf(stderr, | |
"alloc=%d size=%d blocks=%d block_size=%d big=%d objects=%d\n", | |
arena->total_allocs, arena->total_size, arena->total_blocks, | |
arena->total_block_size, arena->total_big_blocks, | |
PyList_Size(arena->a_objects)); | |
*/ | |
#endif | |
block_free(arena->a_head); | |
/* This property normally holds, except when the code being compiled | |
is sys.getobjects(0), in which case there will be two references. | |
assert(arena->a_objects->ob_refcnt == 1); | |
*/ | |
/* Clear all the elements from the list. This is necessary | |
to guarantee that they will be DECREFed. */ | |
r = PyList_SetSlice(arena->a_objects, | |
0, PyList_GET_SIZE(arena->a_objects), NULL); | |
assert(r == 0); | |
assert(PyList_GET_SIZE(arena->a_objects) == 0); | |
Py_DECREF(arena->a_objects); | |
free(arena); | |
} | |
void * | |
PyArena_Malloc(PyArena *arena, size_t size) | |
{ | |
void *p = block_alloc(arena->a_cur, size); | |
if (!p) | |
return PyErr_NoMemory(); | |
#if defined(Py_DEBUG) | |
arena->total_allocs++; | |
arena->total_size += size; | |
#endif | |
/* Reset cur if we allocated a new block. */ | |
if (arena->a_cur->ab_next) { | |
arena->a_cur = arena->a_cur->ab_next; | |
#if defined(Py_DEBUG) | |
arena->total_blocks++; | |
arena->total_block_size += arena->a_cur->ab_size; | |
if (arena->a_cur->ab_size > DEFAULT_BLOCK_SIZE) | |
++arena->total_big_blocks; | |
#endif | |
} | |
return p; | |
} | |
int | |
PyArena_AddPyObject(PyArena *arena, PyObject *obj) | |
{ | |
int r = PyList_Append(arena->a_objects, obj); | |
if (r >= 0) { | |
Py_DECREF(obj); | |
} | |
return r; | |
} |