/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
* Jacob Berkman
*/
#include "camel-memchunk.h"
#include /* memset() */
/*#define TIMEIT*/
#ifdef TIMEIT
#include
#include
struct timeval timeit_start;
static void
time_start (const gchar *desc)
{
gettimeofday (&timeit_start, NULL);
printf ("starting: %s\n", desc);
}
static void
time_end (const gchar *desc)
{
gulong diff;
struct timeval end;
gettimeofday (&end, NULL);
diff = end.tv_sec * 1000 + end.tv_usec / 1000;
diff -= timeit_start.tv_sec * 1000 + timeit_start.tv_usec / 1000;
printf (
"%s took %ld.%03ld seconds\n",
desc, diff / 1000, diff % 1000);
}
#else
#define time_start(x)
#define time_end(x)
#endif
typedef struct _MemChunkFreeNode {
struct _MemChunkFreeNode *next;
guint atoms;
} MemChunkFreeNode;
/**
* CamelMemChunk:
*
* Since: 3.4
**/
struct _CamelMemChunk {
guint blocksize; /* number of atoms in a block */
guint atomsize; /* size of each atom */
GPtrArray *blocks; /* blocks of raw memory */
struct _MemChunkFreeNode *free;
};
/**
* camel_memchunk_new:
* @atomcount: the number of atoms stored in a single malloc'd block of memory
* @atomsize: the size of each allocation
*
* Create a new #CamelMemChunk header. Memchunks are an efficient way to
* allocate and deallocate identical sized blocks of memory quickly, and
* space efficiently.
*
* camel_memchunks are effectively the same as gmemchunks, only faster (much),
* and they use less memory overhead for housekeeping.
*
* Returns: a new #CamelMemChunk
*
* Since: 3.4
**/
CamelMemChunk *
camel_memchunk_new (gint atomcount,
gint atomsize)
{
CamelMemChunk *memchunk = g_malloc (sizeof (*memchunk));
memchunk->blocksize = atomcount;
memchunk->atomsize = MAX (atomsize, sizeof (MemChunkFreeNode));
memchunk->blocks = g_ptr_array_new ();
memchunk->free = NULL;
return memchunk;
}
/**
* camel_memchunk_alloc:
* @memchunk: an #CamelMemChunk
*
* Allocate a new atom size block of memory from an #CamelMemChunk.
* Free the returned atom with camel_memchunk_free().
*
* Returns: an allocated block of memory
*
* Since: 3.4
**/
gpointer
camel_memchunk_alloc (CamelMemChunk *memchunk)
{
gchar *b;
MemChunkFreeNode *f;
gpointer mem;
f = memchunk->free;
if (f) {
f->atoms--;
if (f->atoms > 0) {
mem = ((gchar *) f) + (f->atoms * memchunk->atomsize);
} else {
mem = f;
memchunk->free = memchunk->free->next;
}
return mem;
} else {
b = g_malloc (memchunk->blocksize * memchunk->atomsize);
g_ptr_array_add (memchunk->blocks, b);
f = (MemChunkFreeNode *) &b[memchunk->atomsize];
f->atoms = memchunk->blocksize - 1;
f->next = NULL;
memchunk->free = f;
return b;
}
}
/**
* camel_memchunk_alloc0:
* @memchunk: an #CamelMemChunk
*
* Allocate a new atom size block of memory from an #CamelMemChunk,
* and fill the memory with zeros. Free the returned atom with
* camel_memchunk_free().
*
* Returns: an allocated block of memory
*
* Since: 3.4
**/
gpointer
camel_memchunk_alloc0 (CamelMemChunk *memchunk)
{
gpointer mem;
mem = camel_memchunk_alloc (memchunk);
memset (mem, 0, memchunk->atomsize);
return mem;
}
/**
* camel_memchunk_free:
* @memchunk: an #CamelMemChunk
* @mem: address of atom to free
*
* Free a single atom back to the free pool of atoms in the given
* memchunk.
*
* Since: 3.4
**/
void
camel_memchunk_free (CamelMemChunk *memchunk,
gpointer mem)
{
MemChunkFreeNode *f;
/* Put the location back in the free list. If we knew if the
* preceeding or following cells were free, we could merge the
* free nodes, but it doesn't really add much. */
f = mem;
f->next = memchunk->free;
memchunk->free = f;
f->atoms = 1;
/* We could store the free list sorted - we could then do the above,
* and also probably improve the locality of reference properties for
* the allocator. (And it would simplify some other algorithms at
* that, but slow this one down significantly.) */
}
/**
* camel_memchunk_empty:
* @memchunk: an #CamelMemChunk
*
* Clean out the memchunk buffers. Marks all allocated memory as free blocks,
* but does not give it back to the system. Can be used if the memchunk
* is to be used repeatedly.
*
* Since: 3.4
**/
void
camel_memchunk_empty (CamelMemChunk *memchunk)
{
MemChunkFreeNode *f, *h = NULL;
gint i;
for (i = 0; i < memchunk->blocks->len; i++) {
f = (MemChunkFreeNode *) memchunk->blocks->pdata[i];
f->atoms = memchunk->blocksize;
f->next = h;
h = f;
}
memchunk->free = h;
}
struct _cleaninfo {
struct _cleaninfo *next;
gchar *base;
gint count;
gint size; /* just so tree_search has it, sigh */
};
static gint
tree_compare (struct _cleaninfo *a,
struct _cleaninfo *b)
{
if (a->base < b->base)
return -1;
else if (a->base > b->base)
return 1;
return 0;
}
static gint
tree_search (struct _cleaninfo *a,
gchar *mem)
{
if (a->base <= mem) {
if (mem < &a->base[a->size])
return 0;
return 1;
}
return -1;
}
/**
* camel_memchunk_clean:
* @memchunk: an #CamelMemChunk
*
* Scan all empty blocks and check for blocks which can be free'd
* back to the system.
*
* This routine may take a while to run if there are many allocated
* memory blocks (if the total number of allocations is many times
* greater than atomcount).
*
* Since: 3.4
**/
void
camel_memchunk_clean (CamelMemChunk *memchunk)
{
GTree *tree;
gint i;
MemChunkFreeNode *f;
struct _cleaninfo *ci, *hi = NULL;
f = memchunk->free;
if (memchunk->blocks->len == 0 || f == NULL)
return;
/* first, setup the tree/list so we can map free block addresses to block addresses */
tree = g_tree_new ((GCompareFunc) tree_compare);
for (i = 0; i < memchunk->blocks->len; i++) {
ci = alloca (sizeof (*ci));
ci->count = 0;
ci->base = memchunk->blocks->pdata[i];
ci->size = memchunk->blocksize * memchunk->atomsize;
g_tree_insert (tree, ci, ci);
ci->next = hi;
hi = ci;
}
/* now, scan all free nodes, and count them in their tree node */
while (f) {
ci = g_tree_search (tree, (GCompareFunc) tree_search, f);
if (ci) {
ci->count += f->atoms;
} else {
g_warning ("error, can't find free node in memory block\n");
}
f = f->next;
}
/* if any nodes are all free, free & unlink them */
ci = hi;
while (ci) {
if (ci->count == memchunk->blocksize) {
MemChunkFreeNode *prev = NULL;
f = memchunk->free;
while (f) {
if (tree_search (ci, (gpointer) f) == 0) {
/* prune this node from our free-node list */
if (prev)
prev->next = f->next;
else
memchunk->free = f->next;
} else {
prev = f;
}
f = f->next;
}
g_ptr_array_remove_fast (memchunk->blocks, ci->base);
g_free (ci->base);
}
ci = ci->next;
}
g_tree_destroy (tree);
}
/**
* camel_memchunk_destroy:
* @memchunk: an #CamelMemChunk
*
* Free the memchunk header, and all associated memory.
*
* Since: 3.4
**/
void
camel_memchunk_destroy (CamelMemChunk *memchunk)
{
gint i;
if (memchunk == NULL)
return;
for (i = 0; i < memchunk->blocks->len; i++)
g_free (memchunk->blocks->pdata[i]);
g_ptr_array_free (memchunk->blocks, TRUE);
g_free (memchunk);
}
#if 0
#define CHUNK_SIZE (20)
#define CHUNK_COUNT (32)
#define s(x)
main ()
{
gint i;
MemChunk *mc;
gpointer mem, *last;
GMemChunk *gmc;
struct _EStrv *s;
s = strv_new (8);
s = strv_set (s, 1, "Testing 1");
s = strv_set (s, 2, "Testing 2");
s = strv_set (s, 3, "Testing 3");
s = strv_set (s, 4, "Testing 4");
s = strv_set (s, 5, "Testing 5");
s = strv_set (s, 6, "Testing 7");
for (i = 0; i < 8; i++) {
printf ("s[%d] = %s\n", i, strv_get (s, i));
}
s (sleep (5));
printf ("packing ...\n");
s = strv_pack (s);
for (i = 0; i < 8; i++) {
printf ("s[%d] = %s\n", i, strv_get (s, i));
}
printf ("setting ...\n");
s = strv_set_ref (s, 1, "Testing 1 x");
for (i = 0; i < 8; i++) {
printf ("s[%d] = %s\n", i, strv_get (s, i));
}
printf ("packing ...\n");
s = strv_pack (s);
for (i = 0; i < 8; i++) {
printf ("s[%d] = %s\n", i, strv_get (s, i));
}
strv_free (s);
#if 0
time_start ("Using memchunks");
mc = memchunk_new (CHUNK_COUNT, CHUNK_SIZE);
for (i = 0; i < 1000000; i++) {
mem = memchunk_alloc (mc);
if ((i & 1) == 0)
memchunk_free (mc, mem);
}
s (sleep (10));
memchunk_destroy (mc);
time_end ("allocating 1000000 memchunks, freeing 500k");
time_start ("Using gmemchunks");
gmc = g_mem_chunk_new ("memchunk", CHUNK_SIZE, CHUNK_SIZE * CHUNK_COUNT, G_ALLOC_AND_FREE);
for (i = 0; i < 1000000; i++) {
mem = g_mem_chunk_alloc (gmc);
if ((i & 1) == 0)
g_mem_chunk_free (gmc, mem);
}
s (sleep (10));
g_mem_chunk_destroy (gmc);
time_end ("allocating 1000000 gmemchunks, freeing 500k");
time_start ("Using memchunks");
mc = memchunk_new (CHUNK_COUNT, CHUNK_SIZE);
for (i = 0; i < 1000000; i++) {
mem = memchunk_alloc (mc);
}
s (sleep (10));
memchunk_destroy (mc);
time_end ("allocating 1000000 memchunks");
time_start ("Using gmemchunks");
gmc = g_mem_chunk_new ("memchunk", CHUNK_SIZE, CHUNK_COUNT * CHUNK_SIZE, G_ALLOC_ONLY);
for (i = 0; i < 1000000; i++) {
mem = g_mem_chunk_alloc (gmc);
}
s (sleep (10));
g_mem_chunk_destroy (gmc);
time_end ("allocating 1000000 gmemchunks");
time_start ("Using malloc");
for (i = 0; i < 1000000; i++) {
malloc (CHUNK_SIZE);
}
time_end ("allocating 1000000 malloc");
#endif
}
#endif