[util] Use mmap/munmap for cmem internal alloc/free

This reduces the overhead needed to manage the memory blocks as the
blocks are guaranteed to be page-aligned. Also, the superblock is now
alllocated from within one of the memory blocks it manages. While this
does slightly reduce the available cachelines within the first block (by
one or two depending on 32 vs 64 bit pointers), it removes the need for
an extra memory allocation (probably via malloc) for the superblock.
This commit is contained in:
Bill Currie 2021-07-12 16:33:47 +09:00
parent 12450fe6b8
commit 0a847f92f1
6 changed files with 158 additions and 153 deletions

View file

@ -60,20 +60,11 @@ typedef struct memsline_s {
typedef struct memblock_s {
struct memblock_s *next;
struct memblock_s **prev;
/* The pointer to pass to free()
*/
void *mem;
memline_t *free_lines;
/* Size of memory region before block "header".
*
* Since large blocks are allocated with page-size alignment, odds are
* high that the there will be many cache lines "wasted" in the space
* between the address returned from aligned_alloc (to cache-line
* alignment) and the block itself. Setting them up as a pool makes the
* lines available for smaller allocations, thus reducing waste.
/* Size of memory available within the page-sized block.
*/
size_t pre_size;
/* Size of memory region after block "header".
size_t size;
/* Size of memory region after the page-sized block.
*
* Will be 0 for blocks that were allocated exclusively for small
* allocations, otherwise indicates the size of the allocated block.
@ -85,7 +76,7 @@ typedef struct memblock_s {
#if __WORDSIZE == 64
int pad;
#endif
size_t pre_allocated;
size_t allocated;
} __attribute__((aligned (64))) memblock_t;
typedef struct memsuper_s {

View file

@ -123,6 +123,7 @@ void Sys_MakeCodeWriteable (uintptr_t startaddr, size_t length);
void Sys_PageIn (void *ptr, size_t size);
long Sys_PageSize (void);
void *Sys_Alloc (size_t size);
void Sys_Free (void *mem, size_t size);
//
// system IO

View file

@ -28,21 +28,10 @@
# include "config.h"
#endif
#include <unistd.h>
#include <malloc.h>
#include "QF/alloc.h"
#include "QF/cmem.h"
#include "QF/sys.h"
#ifdef _WIN32
#define cmem_alloc(align, size) _aligned_malloc(size, align)
#define cmem_free(mem) _aligned_free(mem)
#else
#define cmem_alloc(align, size) aligned_alloc(align, size)
#define cmem_free(mem) free(mem)
#endif
static size_t __attribute__((const))
ilog2 (size_t x)
{
@ -53,27 +42,6 @@ ilog2 (size_t x)
return l;
}
memsuper_t *
new_memsuper (void)
{
memsuper_t *super = cmem_alloc (MEM_LINE_SIZE, sizeof (*super));
memset (super, 0, sizeof (*super));
super->page_size = Sys_PageSize ();
super->page_mask = (super->page_size - 1);
return super;
}
void
delete_memsuper (memsuper_t *super)
{
while (super->memblocks) {
memblock_t *t = super->memblocks;
super->memblocks = super->memblocks->next;
cmem_free (t->mem);
}
cmem_free (super);
}
static void
link_free_line (memsuper_t *super, memline_t *line)
{
@ -109,12 +77,8 @@ unlink_line (memline_t *line)
static memblock_t *
init_block (memsuper_t *super, void *mem, size_t alloc_size)
{
size_t size = super->page_size;
size_t mask = super->page_mask;
size_t ptr = (size_t) mem;
memblock_t *block;
memblock_t *block = mem;
block = (memblock_t *) (((ptr + size) & ~mask)) - 1;
memset (block, 0, sizeof (memblock_t));
if (super->memblocks) {
@ -124,26 +88,20 @@ init_block (memsuper_t *super, void *mem, size_t alloc_size)
block->prev = &super->memblocks;
super->memblocks = block;
block->mem = mem;
block->pre_size = (size_t) block - (size_t) mem;
block->post_size = alloc_size - block->pre_size - sizeof (memblock_t);
if (!((size_t) mem & mask) && block->pre_size) {
// can't use the first cache line of the page as it would be
// indistinguishable from a large block
block->pre_size -= MEM_LINE_SIZE;
}
if (block->pre_size) {
memline_t *line = (memline_t *) ((size_t) block - block->pre_size);
block->size = super->page_size - sizeof (*block);
block->post_size = alloc_size - super->page_size;
line->block = block;
line->size = block->pre_size;
memline_t *line = (memline_t *) (block + 1);
line->block_next = 0;
line->block_prev = &block->free_lines;
block->free_lines = line;
line->block = block;
line->size = block->size;
line->block_next = 0;
line->block_prev = &block->free_lines;
block->free_lines = line;
link_free_line (super, line);
link_free_line (super, line);
}
return block;
}
@ -167,8 +125,8 @@ block_alloc (memsuper_t *super, size_t size)
}
size_t page_size = super->page_size;
size_t alloc_size = sizeof (memblock_t) + page_size + size;
void *mem = cmem_alloc (MEM_LINE_SIZE, alloc_size);
size_t alloc_size = page_size + size;
void *mem = Sys_Alloc (alloc_size);
block = init_block (super, mem, alloc_size);
return block;
}
@ -199,7 +157,7 @@ alloc_line (memline_t *line, size_t size)
line->free_next = split;
split->free_prev = &line->free_next;
}
line->block->pre_allocated += line->size;
line->block->allocated += line->size;
unlink_line (line);
return mem;
}
@ -213,7 +171,7 @@ line_free (memsuper_t *super, memblock_t *block, void *mem)
memline_t **l;
memline_t *line = 0;
block->pre_allocated -= size;
block->allocated -= size;
for (l = &block->free_lines; *l; l = &(*l)->block_next) {
line = *l;
@ -303,7 +261,7 @@ cmemalloc (memsuper_t *super, size_t size)
if (!block) {
return 0;
}
return block + 1;
return (void *) ((size_t) block + super->page_size);
} else {
size = 4 << ind;
if (size >= MEM_LINE_SIZE) {
@ -328,7 +286,7 @@ cmemalloc (memsuper_t *super, size_t size)
* allocated line is ever page-aligned as that would make the
* line indistinguishable from a large block.
*/
mem = cmem_alloc (super->page_size, super->page_size);
mem = Sys_Alloc (super->page_size);
// sets super->free_lines, the block is guarnateed to be big
// enough to hold the requested allocation as otherwise a full
// block allocation would have been used
@ -368,12 +326,11 @@ cmemalloc (memsuper_t *super, size_t size)
static void
unlink_block (memblock_t *block)
{
if (block->pre_size) {
if (!block->free_lines || block->free_lines->block_next) {
*(int *) 0 = 0;
}
unlink_line (block->free_lines);
if (!block->free_lines || block->free_lines->block_next) {
*(int *) 0 = 0;
}
unlink_line (block->free_lines);
if (block->next) {
block->next->prev = block->prev;
}
@ -411,17 +368,57 @@ cmemfree (memsuper_t *super, void *mem)
return;
} else if ((size_t) mem & super->page_mask) {
// cache line
size_t page_size = super->page_size;
size_t page_mask = super->page_mask;
block = (memblock_t *) (((size_t) mem + page_size) & ~page_mask) - 1;
block = (memblock_t *) ((size_t) mem & ~super->page_mask);
line_free (super, block, mem);
} else {
// large block
block = (memblock_t *) mem - 1;
block = (memblock_t *) ((size_t) mem - super->page_size);
block->post_free = 1;
}
if (!block->pre_allocated && (!block->post_size || block->post_free)) {
if (!block->allocated && (!block->post_size || block->post_free)) {
unlink_block (block);
cmem_free (block->mem);
Sys_Free (block, super->page_size + block->post_size);
}
}
memsuper_t *
new_memsuper (void)
{
// Temporary superblock used to bootstrap a pool
memsuper_t bootstrap = { };
bootstrap.page_size = Sys_PageSize ();
bootstrap.page_mask = (bootstrap.page_size - 1);
// Allocate the real superblock from the pool. As a superblock is only
// two cache lines large (for 64-byte cache lines), it will always be
// allocated using a block's cache lines, and thus will be inside the first
// block.
memsuper_t *super = cmemalloc (&bootstrap, sizeof (*super));
*super = bootstrap;
// The block used to allocate the real superblock points to the bootstrap
// superblock, but needs to point to the real superblock.
super->memblocks->prev = &super->memblocks;
// Any free cache line block chains will also point to the bootstrap
// block instead of the resl superblock, so fix them up too (there should
// be only one, but no harm in being paranoid)
for (int i = 0; i < MAX_CACHE_LINES; i++) {
if (super->free_lines[i]) {
super->free_lines[i]->free_prev = &super->free_lines[i];
}
}
return super;
}
void
delete_memsuper (memsuper_t *super)
{
// The block holding the superblock is always the last block in the list
while (super->memblocks && super->memblocks->next) {
memblock_t *t = super->memblocks;
super->memblocks = super->memblocks->next;
Sys_Free (t, super->page_size + t->post_size);
}
memblock_t *block = super->memblocks;
Sys_Free (block, super->page_size + block->post_size);
}

View file

@ -650,6 +650,19 @@ Sys_Alloc (size_t size)
#endif
}
VISIBLE void
Sys_Free (void *mem, size_t size)
{
size_t page_size = Sys_PageSize ();
size_t page_mask = page_size - 1;
size = (size + page_mask) & ~page_mask;
#ifdef _WIN32
# error implement Sys_Free for windows
#else
munmap (mem, size);
#endif
}
VISIBLE void
Sys_DebugLog (const char *file, const char *fmt, ...)
{

View file

@ -6,6 +6,8 @@
#include "QF/cmem.h"
#include "QF/set.h"
#define SUPER_LINES (sizeof (memsuper_t) / MEM_LINE_SIZE)
static int
test_block (memsuper_t *super)
{
@ -24,7 +26,7 @@ test_block (memsuper_t *super)
return 0;
}
block = super->memblocks;
if (mem != block + 1) {
if ((size_t) mem != (size_t) block + super->page_size) {
fprintf (stderr, "super does not point to mem\n");
return 0;
}
@ -40,7 +42,7 @@ test_block (memsuper_t *super)
}
memset (mem, 0, size); // valgrind check
cmemfree (super, mem);
if (super->memblocks) {
if ((size_t) super->memblocks + super->page_size == (size_t) mem) {
fprintf (stderr, "super still points to mem\n");
return 0;
}
@ -57,11 +59,11 @@ check_block (memblock_t *block, int line_count, int allocated)
for (memline_t **l = &block->free_lines; *l; l = &(*l)->block_next) {
memline_t *line = *l;
ptrdiff_t ind = (byte *) block - (byte *) line;
ptrdiff_t ind = (byte *) line - (byte *) block;
ind /= MEM_LINE_SIZE;
if (ind < 1 || (size_t ) ind > block->pre_size / MEM_LINE_SIZE) {
fprintf (stderr, "line outside of block: %p %p %p\n",
line, block->mem, block);
if (ind < 1 || (size_t ) (ind - 1) > block->size / MEM_LINE_SIZE) {
fprintf (stderr, "line outside of block: %p %p\n",
line, block);
return 0;
}
if (set_is_member (visited, ind)) {
@ -86,9 +88,9 @@ check_block (memblock_t *block, int line_count, int allocated)
fprintf (stderr, "line block_prev link incorrect\n");
ret = 0;
}
if (line->size > block->pre_size) {
if (line->size > block->size) {
fprintf (stderr, "line size too large: %zd / %zd\n",
line->size, block->pre_size);
line->size, block->size);
ret = 0;
}
if (line->size % MEM_LINE_SIZE) {
@ -101,9 +103,9 @@ check_block (memblock_t *block, int line_count, int allocated)
}
}
if (ret) {
if (free_bytes + block->pre_allocated != block->pre_size) {
if (free_bytes + block->allocated != block->size) {
fprintf (stderr, "block space mismatch: s: %zd a: %zd f: %zd\n",
block->pre_size, block->pre_allocated, free_bytes);
block->size, block->allocated, free_bytes);
ret = 0;
}
if (line_count >= 0 && line_count != count) {
@ -111,9 +113,9 @@ check_block (memblock_t *block, int line_count, int allocated)
line_count, count);
ret = 0;
}
if (allocated >= 0 && (size_t) allocated != block->pre_allocated) {
fprintf (stderr, "pre_allocated wrong size: %zd != %d\n",
block->pre_allocated, allocated);
if (allocated >= 0 && (size_t) allocated != block->allocated) {
fprintf (stderr, "allocated wrong size: %zd != %d\n",
block->allocated, allocated);
}
}
set_delete (visited);
@ -188,19 +190,22 @@ test_line (memsuper_t *super)
fprintf (stderr, "too many memblocks\n");
return 0;
}
if (line1 < (memline_t *) block->mem || line1 >= (memline_t *) block) {
fprintf (stderr, "line1 outside block line pool: %p %p %p\n",
line1, block->mem, block);
if (line1 < (memline_t *) (block + 1)
|| line1 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line1 outside block line pool: %p %p\n",
line1, block);
return 0;
}
if (line2 < (memline_t *) block->mem || line2 >= (memline_t *) block) {
fprintf (stderr, "line2 outside block line pool: %p %p %p\n",
line2, block->mem, block);
if (line2 < (memline_t *) (block + 1)
|| line2 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line2 outside block line pool: %p %p\n",
line2, block);
return 0;
}
if (line3 < (memline_t *) block->mem || line3 >= (memline_t *) block) {
fprintf (stderr, "line3 outside block line pool: %p %p %p\n",
line3, block->mem, block);
if (line3 < (memline_t *) (block + 1)
|| line3 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line3 outside block line pool: %p %p\n",
line3, block);
return 0;
}
if (!((size_t) line1 & super->page_mask)) {
@ -224,7 +229,7 @@ test_line (memsuper_t *super)
return 0;
}
if (!check_block (block, 1, 3 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 1 failed\n");
return 0;
}
@ -235,7 +240,7 @@ test_line (memsuper_t *super)
cmemfree (super, line2);
if (!check_block (block, 2, 2 * MEM_LINE_SIZE)) {
if (!check_block (block, 2, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 2 failed\n");
return 0;
}
@ -254,7 +259,7 @@ test_line (memsuper_t *super)
}
cmemfree (super, line3);
if (!check_block (block, 1, 1 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 3 failed\n");
return 0;
}
@ -269,12 +274,17 @@ test_line (memsuper_t *super)
}
cmemfree (super, line1);
if (super->memblocks) {
fprintf (stderr, "line pool not freed\n");
if (!check_block (block, 1, (0 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 4 failed\n");
return 0;
}
if (!check_bins (super, 0x00)) {
fprintf (stderr, "bins not cleared\n");
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 4 failed\n");
return 0;
}
if (super->free_lines[5] != line1) {
fprintf (stderr, "super free_lines[5] not pointing to line1\n");
return 0;
}
@ -283,7 +293,7 @@ test_line (memsuper_t *super)
line3 = cmemalloc (super, MEM_LINE_SIZE);
block = super->memblocks;
if (!check_block (block, 1, 3 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 4 failed\n");
return 0;
}
@ -294,7 +304,7 @@ test_line (memsuper_t *super)
cmemfree (super, line1);
if (!check_block (block, 2, 2 * MEM_LINE_SIZE)) {
if (!check_block (block, 2, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 5 failed\n");
return 0;
}
@ -305,7 +315,7 @@ test_line (memsuper_t *super)
cmemfree (super, line2);
if (!check_block (block, 2, 1 * MEM_LINE_SIZE)) {
if (!check_block (block, 2, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 6 failed\n");
return 0;
}
@ -315,21 +325,13 @@ test_line (memsuper_t *super)
}
cmemfree (super, line3);
if (super->memblocks) {
fprintf (stderr, "line pool not freed 2\n");
return 0;
}
if (!check_bins (super, 0x00)) {
fprintf (stderr, "bins not cleared 2\n");
return 0;
}
line1 = cmemalloc (super, MEM_LINE_SIZE);
line2 = cmemalloc (super, MEM_LINE_SIZE);
line3 = cmemalloc (super, MEM_LINE_SIZE);
block = super->memblocks;
if (!check_block (block, 1, 3 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 7 failed\n");
return 0;
}
@ -340,7 +342,7 @@ test_line (memsuper_t *super)
cmemfree (super, line3);
if (!check_block (block, 1, 2 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 8 failed\n");
return 0;
}
@ -351,7 +353,7 @@ test_line (memsuper_t *super)
cmemfree (super, line2);
if (!check_block (block, 1, 1 * MEM_LINE_SIZE)) {
if (!check_block (block, 1, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 9 failed\n");
return 0;
}
@ -361,14 +363,6 @@ test_line (memsuper_t *super)
}
cmemfree (super, line1);
if (super->memblocks) {
fprintf (stderr, "line pool not freed 3\n");
return 0;
}
if (!check_bins (super, 0x00)) {
fprintf (stderr, "bins not cleared 3\n");
return 0;
}
return 1;
}
@ -481,16 +475,16 @@ test_block_line (memsuper_t *super)
void *line;
memblock_t *block = super->memblocks;
if (block + 1 != (memblock_t *) mem) {
if ((size_t) block + super->page_size != (size_t) mem) {
fprintf (stderr, "super memblocks do not point to mem\n");
return 0;
}
if (block->pre_size < MEM_LINE_SIZE) {
if (block->size < MEM_LINE_SIZE) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "can't allocate line from block\n");
return 0;
}
if (block->next) {
if (block->next != (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "excess blocks in super\n");
return 0;
}
@ -499,7 +493,7 @@ test_block_line (memsuper_t *super)
fprintf (stderr, "line is page aligned\n");
return 0;
}
if (super->memblocks->next) {
if (0 && super->memblocks->next) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "mem and line not in same block\n");
return 0;
@ -513,7 +507,9 @@ test_block_line (memsuper_t *super)
fprintf (stderr, "block not reused for mem\n");
return 0;
}
if (super->memblocks != block || super->memblocks->next) {
//if (super->memblocks != block || super->memblocks->next) {
if (super->memblocks != block || !super->memblocks->next
|| super->memblocks->next->next) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "blocks corrupt\n");
return 0;
@ -524,7 +520,7 @@ test_block_line (memsuper_t *super)
return 0;
}
cmemfree (super, mem);
if (super->memblocks) {
if (0 && super->memblocks) {
fprintf (stderr, "shared block not freed\n");
return 0;
}
@ -588,8 +584,9 @@ main (void)
super->page_mask);
return 1;
}
if (super->memblocks) {
fprintf (stderr, "super block list not null\n");
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block a: %p %p\n", super->memblocks, super);
return 1;
}
for (i = 4; i-- > 0; ) {
@ -601,6 +598,7 @@ main (void)
fprintf (stderr, "super last_freed not all null\n");
return 1;
}
#if 0 // no longer valid
for (i = MAX_CACHE_LINES; i-- > 0; ) {
if (super->free_lines[i]) {
break;
@ -610,12 +608,14 @@ main (void)
fprintf (stderr, "super free_lines not all null\n");
return 1;
}
#endif
if (!test_block (super)) {
fprintf (stderr, "block tests failed\n");
return 1;
}
if (super->memblocks) {
fprintf (stderr, "super block list not null 2\n");
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block b: %p %p\n", super->memblocks, super);
return 1;
}
for (i = 4; i-- > 0; ) {
@ -627,6 +627,7 @@ main (void)
fprintf (stderr, "super last_freed not all null 2\n");
return 1;
}
#if 0 // no longer valid
for (i = MAX_CACHE_LINES; i-- > 0; ) {
if (super->free_lines[i]) {
break;
@ -636,12 +637,14 @@ main (void)
fprintf (stderr, "super free_lines not all null 2\n");
return 1;
}
#endif
if (!test_line (super)) {
fprintf (stderr, "line tests failed\n");
return 1;
}
if (super->memblocks) {
fprintf (stderr, "super block list not null 2\n");
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block c: %p %p\n", super->memblocks, super);
return 1;
}
if (!test_block_line (super)) {

View file

@ -518,8 +518,8 @@ PortalCompleted (threaddata_t *thread, portal_t *completed)
static void
dump_super_stats (int id, memsuper_t *super)
{
size_t total_pre_size = 0;
size_t total_pre_allocated = 0;
size_t total_size = 0;
size_t total_allocated = 0;
size_t total_post_size = 0;
size_t total_post_allocated = 0;
size_t num_blocks = 0;
@ -527,8 +527,8 @@ dump_super_stats (int id, memsuper_t *super)
for (memblock_t *block = super->memblocks; block; block = block->next) {
num_blocks++;
total_pre_size += block->pre_size;
total_pre_allocated += block->pre_allocated;
total_size += block->size;
total_allocated += block->allocated;
total_post_size += block->post_size;
// post_free is a flag
total_post_allocated += !block->post_free * block->post_size;
@ -544,8 +544,8 @@ dump_super_stats (int id, memsuper_t *super)
WRLOCK (global_lock);
printf ("cmem stats for thread %d\n", id);
printf (" blocks: %zd\n", num_blocks);
printf (" pre: s:%-8zd a:%-8zd f:%-8zd\n", total_pre_size,
total_pre_allocated, total_pre_size - total_pre_allocated);
printf (" : s:%-8zd a:%-8zd f:%-8zd\n", total_size,
total_allocated, total_size - total_allocated);
printf (" post: s:%-8zd a:%-8zd f:%-8zd\n", total_post_size,
total_post_allocated, total_post_size - total_post_allocated);
printf (" ");