view src/lib/mempool-alloconly.c @ 3238:889a584bd953 HEAD

Changed alloconly pools to call malloc() only once when creating the pool. Pool names aren't used anymore without DEBUG. Don't immediately double the pool's initial size for the first allocated block.
author Timo Sirainen <tss@iki.fi>
date Sun, 27 Mar 2005 19:38:38 +0300
parents 6396b4c0a721
children 4cbb7b67bc98
line wrap: on
line source

/* Copyright (c) 2002-2003 Timo Sirainen */

/* @UNSAFE: whole file */
#include "lib.h"
#include "mempool.h"

#include <stdlib.h>

#ifdef HAVE_GC_GC_H
#  include <gc/gc.h>
#elif defined (HAVE_GC_H)
#  include <gc.h>
#endif

#define MAX_ALLOC_SIZE SSIZE_T_MAX

struct alloconly_pool {
	struct pool pool;
	int refcount;

	size_t base_size;
	struct pool_block *block;
#ifdef DEBUG
	const char *name;
#endif
};

struct pool_block {
	struct pool_block *prev;

	size_t size;
	size_t left;
	size_t last_alloc_size;

	/* unsigned char data[]; */
};
#define SIZEOF_POOLBLOCK (MEM_ALIGN(sizeof(struct pool_block)))

#define POOL_BLOCK_DATA(block) \
	((char *) (block) + SIZEOF_POOLBLOCK)

static const char *pool_alloconly_get_name(pool_t pool);
static void pool_alloconly_ref(pool_t pool);
static void pool_alloconly_unref(pool_t pool);
static void *pool_alloconly_malloc(pool_t pool, size_t size);
static void pool_alloconly_free(pool_t pool, void *mem);
static void *pool_alloconly_realloc(pool_t pool, void *mem,
				    size_t old_size, size_t new_size);
static void pool_alloconly_clear(pool_t pool);
static size_t pool_alloconly_get_max_easy_alloc_size(pool_t pool);

static void block_alloc(struct alloconly_pool *pool, size_t size);

static struct pool static_alloconly_pool = {
	pool_alloconly_get_name,

	pool_alloconly_ref,
	pool_alloconly_unref,

	pool_alloconly_malloc,
	pool_alloconly_free,

	pool_alloconly_realloc,

	pool_alloconly_clear,
	pool_alloconly_get_max_easy_alloc_size,

	TRUE,
	FALSE
};

pool_t pool_alloconly_create(const char *name, size_t size)
{
	struct alloconly_pool apool, *new_apool;
	size_t min_alloc = sizeof(struct alloconly_pool) + SIZEOF_POOLBLOCK;

#ifdef DEBUG
	min_alloc += strlen(name) + 1;
#endif

	/* create a fake alloconly_pool so we can call block_alloc() */
	memset(&apool, 0, sizeof(apool));
	apool.pool = static_alloconly_pool;
	apool.refcount = 1;

	if (size < min_alloc)
		size = nearest_power(size + min_alloc);
	block_alloc(&apool, size);

	/* now allocate the actual alloconly_pool from the created block */
	new_apool = p_new(&apool.pool, struct alloconly_pool, 1);
	*new_apool = apool;
#ifdef DEBUG
	new_apool->name = p_strdup(&new_apool->pool, name);
#endif

	/* set base_size so p_clear() doesn't trash alloconly_pool structure. */
	new_apool->base_size = new_apool->block->size - new_apool->block->left;
	new_apool->block->last_alloc_size = 0;

	return &new_apool->pool;
}

static void pool_alloconly_destroy(struct alloconly_pool *apool)
{
	void *block;

	/* destroy all but the last block */
	pool_alloconly_clear(&apool->pool);

	/* destroy the last block */
	block = apool->block;
#ifdef DEBUG
	memset(block, 0xde, SIZEOF_POOLBLOCK + apool->block->size);
#endif

#ifndef USE_GC
	free(block);
#endif
}

static const char *pool_alloconly_get_name(pool_t pool)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;

#ifdef DEBUG
	return apool->name;
#else
	return "alloconly";
#endif
}

static void pool_alloconly_ref(pool_t pool)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;

	apool->refcount++;
}

static void pool_alloconly_unref(pool_t pool)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;

	if (--apool->refcount == 0)
		pool_alloconly_destroy(apool);
}

static void block_alloc(struct alloconly_pool *apool, size_t size)
{
	struct pool_block *block;

	i_assert(size > SIZEOF_POOLBLOCK);

	if (apool->block != NULL) {
		/* each block is at least twice the size of the previous one */
		if (size <= apool->block->size)
			size += apool->block->size;

		size = nearest_power(size);
#ifdef DEBUG
		i_warning("Growing pool '%s' with: %"PRIuSIZE_T,
			  apool->name, size);
#endif
	}

#ifndef USE_GC
	block = calloc(size, 1);
#else
	block = GC_malloc(size);
	memset(block, 0, size);
#endif
	if (block == NULL)
		i_fatal_status(FATAL_OUTOFMEM, "block_alloc(): Out of memory");
	block->prev = apool->block;
	apool->block = block;

	block->size = size - SIZEOF_POOLBLOCK;
	block->left = block->size;
}

static void *pool_alloconly_malloc(pool_t pool, size_t size)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;
	void *mem;

	if (size == 0 || size > SSIZE_T_MAX)
		i_panic("Trying to allocate %"PRIuSIZE_T" bytes", size);

	size = MEM_ALIGN(size);

	if (apool->block->left < size) {
		/* we need a new block */
		block_alloc(apool, size + SIZEOF_POOLBLOCK);
	}

	mem = POOL_BLOCK_DATA(apool->block) +
		(apool->block->size - apool->block->left);

	apool->block->left -= size;
	apool->block->last_alloc_size = size;
	return mem;
}

static void pool_alloconly_free(pool_t pool, void *mem)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;

	/* we can free only the last allocation */
	if (POOL_BLOCK_DATA(apool->block) +
	    (apool->block->size - apool->block->left -
	     apool->block->last_alloc_size) == mem) {
		memset(mem, 0, apool->block->last_alloc_size);
		apool->block->left += apool->block->last_alloc_size;
                apool->block->last_alloc_size = 0;
	}
}

static int pool_try_grow(struct alloconly_pool *apool, void *mem, size_t size)
{
	/* see if we want to grow the memory we allocated last */
	if (POOL_BLOCK_DATA(apool->block) +
	    (apool->block->size - apool->block->left -
	     apool->block->last_alloc_size) == mem) {
		/* yeah, see if we can grow */
		if (apool->block->left >= size-apool->block->last_alloc_size) {
			/* just shrink the available size */
			apool->block->left -=
				size - apool->block->last_alloc_size;
			apool->block->last_alloc_size = size;
			return TRUE;
		}
	}

	return FALSE;
}

static void *pool_alloconly_realloc(pool_t pool, void *mem,
				    size_t old_size, size_t new_size)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;
	unsigned char *new_mem;

	if (new_size == 0 || new_size > SSIZE_T_MAX)
		i_panic("Trying to allocate %"PRIuSIZE_T" bytes", new_size);

	if (mem == NULL)
		return pool_alloconly_malloc(pool, new_size);

	if (new_size <= old_size)
		return mem;

	new_size = MEM_ALIGN(new_size);

	/* see if we can directly grow it */
	if (!pool_try_grow(apool, mem, new_size)) {
		/* slow way - allocate + copy */
		new_mem = pool_alloconly_malloc(pool, new_size);
		memcpy(new_mem, mem, old_size);
		mem = new_mem;
	}

        return mem;
}

static void pool_alloconly_clear(pool_t pool)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;
	struct pool_block *block;
	size_t avail_size;

	/* destroy all blocks but the oldest, which contains the
	   struct alloconly_pool allocation. */
	while (apool->block->prev != NULL) {
		block = apool->block;
		apool->block = block->prev;

#ifdef DEBUG
		memset(block, 0xde, SIZEOF_POOLBLOCK + block->size);
#endif
#ifndef USE_GC
		free(block);
#endif
	}

	/* clear the first block */
	avail_size = apool->block->size - apool->base_size;
	memset(PTR_OFFSET(POOL_BLOCK_DATA(apool->block), apool->base_size), 0,
	       avail_size - apool->block->left);
	apool->block->left = avail_size;
	apool->block->last_alloc_size = 0;
}

static size_t pool_alloconly_get_max_easy_alloc_size(pool_t pool)
{
	struct alloconly_pool *apool = (struct alloconly_pool *) pool;

	return apool->block->left;
}