/* * The system's VMM page size can be obtained on most unices with a * getpagesize() call or deduced from various header files. To make * things simpler, we assume that it is 4K, which is OK for most systems. * It is probably better if this is the native page size, but it doesn't * have to be. In theory, if SYSTEM_PAGE_SIZE is larger than the native page * size, then `POOL_ADDR(p)->arenaindex' could rarely cause a segmentation * violation fault. 4K is apparently OK for all the platforms that python * currently targets. */ #define SYSTEM_PAGE_SIZE (4 * 1024) #define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)
/* * Size of the pools used for small blocks. Should be a power of 2, * between 1K and SYSTEM_PAGE_SIZE, that is: 1k, 2k, 4k. */ #define POOL_SIZE SYSTEM_PAGE_SIZE /* must be 2^N */ #define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK
/* When you say memory, my mind reasons in terms of (pointers to) blocks */ typedefuint8_t block;
/* Pool for small blocks. */ structpool_header { union { block *_padding; uint count; } ref; /* number of allocated blocks */ block *freeblock; /* pool's free list head */ structpool_header *nextpool;/* next pool of this size class */ structpool_header *prevpool;/* previous pool "" */ uint arenaindex; /* index into arenas of base adr */ uint szidx; /* block size class index */ uint nextoffset; /* bytes to virgin block */ uint maxnextoffset; /* largest valid nextoffset */ };
/* Record keeping for arenas. */ structarena_object { /* The address of the arena, as returned by malloc. Note that 0 * will never be returned by a successful malloc, and is used * here to mark an arena_object that doesn't correspond to an * allocated arena. */ uintptr_t address;
/* Pool-aligned pointer to the next pool to be carved off. */ block* pool_address;
/* The number of available pools in the arena: free pools + never- * allocated pools. */ uint nfreepools;
/* The total number of pools in the arena, whether or not available. */ uint ntotalpools;
/* Singly-linked list of available pools. */ structpool_header* freepools;
/* Whenever this arena_object is not associated with an allocated * arena, the nextarena member is used to link all unassociated * arena_objects in the singly-linked `unused_arena_objects` list. * The prevarena member is unused in this case. * * When this arena_object is associated with an allocated arena * with at least one available pool, both members are used in the * doubly-linked `usable_arenas` list, which is maintained in * increasing order of `nfreepools` values. * * Else this arena_object is associated with an allocated arena * all of whose pools are in use. `nextarena` and `prevarena` * are both meaningless in this case. */ structarena_object* nextarena; structarena_object* prevarena; };
/* Array of objects used to track chunks of memory (arenas). */ staticstructarena_object* arenas = NULL; /* Number of slots currently allocated in the `arenas` vector. */ static uint maxarenas = 0;
/* The head of the singly-linked, NULL-terminated list of available * arena_objects. */ staticstructarena_object* unused_arena_objects = NULL;
/* The head of the doubly-linked, NULL-terminated at each end, list of * arena_objects associated with arenas that have pools available. */ staticstructarena_object* usable_arenas = NULL;
/* nfp2lasta[nfp] is the last arena in usable_arenas with nfp free pools */ staticstructarena_object* nfp2lasta[MAX_POOLS_IN_ARENA + 1] = {NULL };
/* How many arena_objects do we initially allocate? * 16 = can allocate 16 arenas = 16 * ARENA_SIZE = 4MB before growing the * `arenas` vector. */ #define INITIAL_ARENA_OBJECTS 16
/* Number of arenas allocated that haven't been free()'d. */ staticsize_t narenas_currently_allocated = 0;
/* Total number of times malloc() called to allocate an arena. */ staticsize_t ntimes_arena_allocated = 0; /* High water mark (max value ever seen) for narenas_currently_allocated. */ staticsize_t narenas_highwater = 0;
/* Allocate a new arena. If we run out of memory, return NULL. Else * allocate a new arena, and return the address of an arena_object * describing the new arena. It's expected that the caller will set * `usable_arenas` to the return value. */ staticstructarena_object* new_arena(void) { structarena_object* arenaobj; uint excess; /* number of bytes above pool alignment */ void *address; staticint debug_stats = -1;
/* Double the number of arena objects on each allocation. * Note that it's possible for `numarenas` to overflow. */ numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS; if (numarenas <= maxarenas) returnNULL; /* overflow */ #if SIZEOF_SIZE_T <= SIZEOF_INT if (numarenas > SIZE_MAX / sizeof(*arenas)) returnNULL; /* overflow */ #endif nbytes = numarenas * sizeof(*arenas); arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes); if (arenaobj == NULL) returnNULL; arenas = arenaobj;
/* We might need to fix pointers that were copied. However, * new_arena only gets called when all the pages in the * previous arenas are full. Thus, there are *no* pointers * into the old array. Thus, we don't have to worry about * invalid pointers. Just to be sure, some asserts: */ assert(usable_arenas == NULL); assert(unused_arena_objects == NULL);
/* Put the new arenas on the unused_arena_objects list. */ for (i = maxarenas; i < numarenas; ++i) { arenas[i].address = 0; /* mark as unassociated */ arenas[i].nextarena = i < numarenas - 1 ? &arenas[i+1] : NULL; }
/* Take the next available arena object off the head of the list. */ assert(unused_arena_objects != NULL); arenaobj = unused_arena_objects; unused_arena_objects = arenaobj->nextarena; assert(arenaobj->address == 0); address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE); if (address == NULL) { /* The allocation failed: return NULL after putting the * arenaobj back. */ arenaobj->nextarena = unused_arena_objects; unused_arena_objects = arenaobj; returnNULL; } arenaobj->address = (uintptr_t)address;
++narenas_currently_allocated; ++ntimes_arena_allocated; if (narenas_currently_allocated > narenas_highwater) narenas_highwater = narenas_currently_allocated; arenaobj->freepools = NULL; /* pool_address <- first pool-aligned address in the arena nfreepools <- number of whole pools that fit after alignment */ arenaobj->pool_address = (block*)arenaobj->address; arenaobj->nfreepools = MAX_POOLS_IN_ARENA; excess = (uint)(arenaobj->address & POOL_SIZE_MASK); if (excess != 0) { --arenaobj->nfreepools; arenaobj->pool_address += POOL_SIZE - excess; } arenaobj->ntotalpools = arenaobj->nfreepools;
/* called when pymalloc_alloc can not allocate a block from usedpool. * This function takes new pool and allocate a block from it. */ staticvoid* allocate_from_new_pool(uint size) { /* There isn't a pool of the right size class immediately * available: use a free pool. */ if (UNLIKELY(usable_arenas == NULL)) { /* No arena has a free pool: allocate a new arena. */ #ifdef WITH_MEMORY_LIMITS if (narenas_currently_allocated >= MAX_ARENAS) { returnNULL; } #endif usable_arenas = new_arena(); if (usable_arenas == NULL) { returnNULL; } usable_arenas->nextarena = usable_arenas->prevarena = NULL; assert(nfp2lasta[usable_arenas->nfreepools] == NULL); nfp2lasta[usable_arenas->nfreepools] = usable_arenas; } assert(usable_arenas->address != 0);
/* This arena already had the smallest nfreepools value, so decreasing * nfreepools doesn't change that, and we don't need to rearrange the * usable_arenas list. However, if the arena becomes wholly allocated, * we need to remove its arena_object from usable_arenas. */ assert(usable_arenas->nfreepools > 0); if (nfp2lasta[usable_arenas->nfreepools] == usable_arenas) { /* It's the last of this size, so there won't be any. */ nfp2lasta[usable_arenas->nfreepools] = NULL; } /* If any free pools will remain, it will be the new smallest. */ if (usable_arenas->nfreepools > 1) { assert(nfp2lasta[usable_arenas->nfreepools - 1] == NULL); nfp2lasta[usable_arenas->nfreepools - 1] = usable_arenas; }
/* Try to get a cached free pool. */ poolp pool = usable_arenas->freepools; if (LIKELY(pool != NULL)) { /* Unlink from cached pools. */ usable_arenas->freepools = pool->nextpool; usable_arenas->nfreepools--; if (UNLIKELY(usable_arenas->nfreepools == 0)) { /* Wholly allocated: remove. */ assert(usable_arenas->freepools == NULL); assert(usable_arenas->nextarena == NULL || usable_arenas->nextarena->prevarena == usable_arenas); usable_arenas = usable_arenas->nextarena; if (usable_arenas != NULL) { usable_arenas->prevarena = NULL; assert(usable_arenas->address != 0); } } else { /* nfreepools > 0: it must be that freepools * isn't NULL, or that we haven't yet carved * off all the arena's pools for the first * time. */ assert(usable_arenas->freepools != NULL || usable_arenas->pool_address <= (block*)usable_arenas->address + ARENA_SIZE - POOL_SIZE); } } else { /* Carve off a new pool. */ assert(usable_arenas->nfreepools > 0); assert(usable_arenas->freepools == NULL); pool = (poolp)usable_arenas->pool_address; assert((block*)pool <= (block*)usable_arenas->address + ARENA_SIZE - POOL_SIZE); pool->arenaindex = (uint)(usable_arenas - arenas); assert(&arenas[pool->arenaindex] == usable_arenas); pool->szidx = DUMMY_SIZE_IDX; usable_arenas->pool_address += POOL_SIZE; --usable_arenas->nfreepools;
if (usable_arenas->nfreepools == 0) { assert(usable_arenas->nextarena == NULL || usable_arenas->nextarena->prevarena == usable_arenas); /* Unlink the arena: it is completely allocated. */ usable_arenas = usable_arenas->nextarena; if (usable_arenas != NULL) { usable_arenas->prevarena = NULL; assert(usable_arenas->address != 0); } } }
/* Frontlink to used pools. */ block *bp; poolp next = usedpools[size + size]; /* == prev */ pool->nextpool = next; pool->prevpool = next; next->nextpool = pool; next->prevpool = pool; pool->ref.count = 1; if (pool->szidx == size) { /* Luckily, this pool last contained blocks * of the same size class, so its header * and free list are already initialized. */ bp = pool->freeblock; assert(bp != NULL); pool->freeblock = *(block **)bp; return bp; } /* * Initialize the pool header, set up the free list to * contain just the second block, and return the first * block. */ pool->szidx = size; size = INDEX2SIZE(size); bp = (block *)pool + POOL_OVERHEAD; pool->nextoffset = POOL_OVERHEAD + (size << 1); pool->maxnextoffset = POOL_SIZE - size; pool->freeblock = bp + size; *(block **)(pool->freeblock) = NULL; return bp; }
/* pymalloc allocator Return a pointer to newly allocated memory if pymalloc allocated memory. Return NULL if pymalloc failed to allocate the memory block: on bigger requests, on error in the code below (as a last chance to serve the request) or when the max memory limit has been reached. */ staticinlinevoid* pymalloc_alloc(void *ctx, size_t nbytes) { #ifdef WITH_VALGRIND if (UNLIKELY(running_on_valgrind == -1)) { running_on_valgrind = RUNNING_ON_VALGRIND; } if (UNLIKELY(running_on_valgrind)) { returnNULL; } #endif
if (UNLIKELY(nbytes == 0)) { returnNULL; } if (UNLIKELY(nbytes > SMALL_REQUEST_THRESHOLD)) { returnNULL; }
if (LIKELY(pool != pool->nextpool)) { /* * There is a used pool for this size class. * Pick up the head block of its free list. */ ++pool->ref.count; bp = pool->freeblock; assert(bp != NULL);
if (UNLIKELY((pool->freeblock = *(block **)bp) == NULL)) { // Reached the end of the free list, try to extend it. pymalloc_pool_extend(pool, size); } } else { /* There isn't a pool of the right size class immediately * available: use a free pool. */ bp = allocate_from_new_pool(size); }