Commit b99032d7 authored by Ell's avatar Ell

buffer: add gegl-scratch allocator

gegl-scratch is a fast memory allocator, suitable for small (up to
a few megabytes), short-lived (usually, but not necessarily, bound
to the current stack-frame) allocations.  Unlike alloca(), gimp-
scratch doesn't use the stack, and is therefore safer, may outlive
the current stackframe, and will also serve bigger requests, by
falling-back to malloc().

The allocator itself is very simple:  We keep a per-thread stack of
cached memory blocks (allocated using the normal allocator).  When
serving an allocation request, we simply pop the top block off the
stack, and return it.  If the block is too small, we replace it
with a big-enough block; if the stack is empty, we allocate a new
block.  When the block is freed, we push it back to the top of the
stack.  The idea is that the stacks will ultimately stabalize to
contain blocks that can serve all the encountered allocation
patterns, without needing to reisze any of the blocks; as a
consequence, the amount of scratch memory allocated at any given
time should really be kept to a minimum.

Note that blocks may be allocated on one thread, and freed on
another thread, without leaking memory, however, this is not an
intended usage pattern, and may lead to suboptimal performance.

This code was migrated from GIMP.
parent a1707af4
......@@ -65,6 +65,7 @@ GEGL_introspectable_headers = \
buffer/gegl-buffer-swap.h \
buffer/gegl-memory.h \
buffer/gegl-rectangle.h \
buffer/gegl-scratch.h \
buffer/gegl-tile-backend.h \
buffer/gegl-tile-handler.h \
buffer/gegl-tile-source.h \
......
......@@ -53,6 +53,7 @@ libbuffer_la_SOURCES = \
gegl-sampler-nearest.c \
gegl-sampler-nohalo.c \
gegl-sampler-lohalo.c \
gegl-scratch.c \
gegl-tile.c \
gegl-tile-source.c \
gegl-tile-storage.c \
......@@ -90,6 +91,8 @@ libbuffer_la_SOURCES = \
gegl-sampler-nearest.h \
gegl-sampler-nohalo.h \
gegl-sampler-lohalo.h \
gegl-scratch.h \
gegl-scratch-private.h \
gegl-tile.h \
gegl-tile-source.h \
gegl-tile-storage.h \
......
......@@ -736,6 +736,7 @@ gegl_buffer_flush_ext (GeglBuffer *buffer, const GeglRectangle *rect);
#include "gegl-buffer-iterator.h"
#include "gegl-rectangle.h"
#include "gegl-memory.h"
#include "gegl-scratch.h"
GType gegl_buffer_get_type (void) G_GNUC_CONST;
......
/* This file is part of GEGL
*
* GEGL 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; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL 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 GEGL; if not, see <https://www.gnu.org/licenses/>.
*
* Copyright 2019 Ell
*/
#ifndef __GEGL_SCRATCH_PRIVATE_H__
#define __GEGL_SCRATCH_PRIVATE_H__
guint64 gegl_scratch_get_total (void);
#endif /* __GEGL_SCRATCH_PRIVATE_H__ */
/* This file is part of GEGL
*
* GEGL 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; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL 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 GEGL; if not, see <https://www.gnu.org/licenses/>.
*
* Copyright 2019 Ell
*/
#include "config.h"
#include <string.h>
#include <glib-object.h>
#include "gegl-memory-private.h"
#include "gegl-scratch.h"
#include "gegl-scratch-private.h"
#define GEGL_SCRATCH_ALIGNMENT GEGL_ALIGN
#define GEGL_SCRATCH_MAX_BLOCK_SIZE (1 << 20)
#define GEGL_SCRATCH_BLOCK_DATA_OFFSET ((sizeof (GeglScratchBlockHeader) + \
(GEGL_SCRATCH_ALIGNMENT - 1)) / \
GEGL_SCRATCH_ALIGNMENT * \
GEGL_SCRATCH_ALIGNMENT)
G_STATIC_ASSERT (GEGL_SCRATCH_ALIGNMENT <= G_MAXUINT8);
/* private types */
typedef struct _GeglScratchBlockHeader GeglScratchBlockHeader;
typedef struct _GeglScratchBlock GeglScratchBlock;
typedef struct _GeglScratchContext GeglScratchContext;
struct _GeglScratchBlockHeader
{
GeglScratchContext *context;
gsize size;
guint8 offset;
};
struct _GeglScratchBlock
{
GeglScratchBlockHeader header;
guint8 padding[GEGL_SCRATCH_BLOCK_DATA_OFFSET -
sizeof (GeglScratchBlockHeader)];
guint8 data[];
};
struct _GeglScratchContext
{
GeglScratchBlock **blocks;
gint n_blocks;
gint n_available_blocks;
};
/* local function prototypes */
static GeglScratchContext * gegl_scratch_context_new (void);
static void gegl_scratch_context_free (GeglScratchContext *context);
static GeglScratchBlock * gegl_scratch_block_new (GeglScratchContext *context,
gsize size);
static void gegl_scratch_block_free (GeglScratchBlock *block);
/* local variables */
GPrivate gegl_scratch_context =
G_PRIVATE_INIT ((GDestroyNotify) gegl_scratch_context_free);
static volatile guintptr gegl_scratch_total;
/* private functions */
GeglScratchContext *
gegl_scratch_context_new (void)
{
return g_slice_new0 (GeglScratchContext);
}
void
gegl_scratch_context_free (GeglScratchContext *context)
{
gint i;
for (i = 0; i < context->n_available_blocks; i++)
gegl_scratch_block_free (context->blocks[i]);
g_free (context->blocks);
g_slice_free (GeglScratchContext, context);
}
GeglScratchBlock *
gegl_scratch_block_new (GeglScratchContext *context,
gsize size)
{
GeglScratchBlock *block;
gint offset;
g_atomic_pointer_add (&gegl_scratch_total, +size);
block = g_malloc ((GEGL_SCRATCH_ALIGNMENT - 1) +
sizeof (GeglScratchBlock) +
size);
offset = GEGL_SCRATCH_ALIGNMENT -
((guintptr) block) % GEGL_SCRATCH_ALIGNMENT;
offset %= GEGL_SCRATCH_ALIGNMENT;
block = (GeglScratchBlock *) ((guint8 *) block + offset);
block->header.context = context;
block->header.size = size;
block->header.offset = offset;
return block;
}
void
gegl_scratch_block_free (GeglScratchBlock *block)
{
g_atomic_pointer_add (&gegl_scratch_total, -block->header.size);
g_free ((guint8 *) block - block->header.offset);
}
/* public functions */
gpointer
gegl_scratch_alloc (gsize size)
{
GeglScratchContext *context;
GeglScratchBlock *block;
if (G_UNLIKELY (! size))
return NULL;
if (G_UNLIKELY (size > GEGL_SCRATCH_MAX_BLOCK_SIZE))
{
block = gegl_scratch_block_new (NULL, size);
return block->data;
}
context = g_private_get (&gegl_scratch_context);
if (G_UNLIKELY (! context))
{
context = gegl_scratch_context_new ();
g_private_set (&gegl_scratch_context, context);
}
if (G_LIKELY (context->n_available_blocks))
{
block = context->blocks[--context->n_available_blocks];
if (G_LIKELY (size <= block->header.size))
return block->data;
gegl_scratch_block_free (block);
}
block = gegl_scratch_block_new (context, size);
return block->data;
}
gpointer
gegl_scratch_alloc0 (gsize size)
{
gpointer ptr;
if (G_UNLIKELY (! size))
return NULL;
ptr = gegl_scratch_alloc (size);
memset (ptr, 0, size);
return ptr;
}
void
gegl_scratch_free (gpointer ptr)
{
GeglScratchContext *context;
GeglScratchBlock *block;
if (G_UNLIKELY (! ptr))
return;
context = g_private_get (&gegl_scratch_context);
block = (GeglScratchBlock *) ((guint8 *) ptr -
GEGL_SCRATCH_BLOCK_DATA_OFFSET);
if (G_UNLIKELY (block->header.context != context))
{
gegl_scratch_block_free (block);
return;
}
if (G_UNLIKELY (context->n_available_blocks == context->n_blocks))
{
context->n_blocks = MAX (2 * context->n_blocks, 1);
context->blocks = g_renew (GeglScratchBlock *, context->blocks,
context->n_blocks);
}
context->blocks[context->n_available_blocks++] = block;
}
/* public functions (stats) */
guint64
gegl_scratch_get_total (void)
{
return gegl_scratch_total;
}
/* This file is part of GEGL
*
* GEGL 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; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL 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 GEGL; if not, see <https://www.gnu.org/licenses/>.
*
* Copyright 2019 Ell
*/
#ifndef __GEGL_SCRATCH_H__
#define __GEGL_SCRATCH_H__
/**
* gegl_scratch_alloc: (skip)
* @size: the number of bytes to allocte.
*
* Allocates @size bytes of scratch memory.
* If @size is 0 it returns NULL.
*
* Returns a pointer to the allocated memory.
*/
gpointer gegl_scratch_alloc (gsize size) G_GNUC_MALLOC;
/**
* gegl_scratch_alloc0: (skip)
* @size: the number of bytes to allocte.
*
* Allocates @size bytes of scratch memory, initialized to zero.
* If @size is 0 it returns NULL.
*
* Returns a pointer to the allocated memory.
*/
gpointer gegl_scratch_alloc0 (gsize size) G_GNUC_MALLOC;
/**
* gegl_scratch_free: (skip)
* @ptr: the memory to free.
*
* Frees the memory pointed to by @ptr.
* If @ptr is NULL, does nothing.
*
* The memory must have been allocated using one of the scratch-memory
* allocation functions.
*/
void gegl_scratch_free (gpointer ptr);
#define _GEGL_SCRATCH_MUL(x, y) \
(G_LIKELY ((y) == 0 || (x) <= G_MAXSIZE / (y)) ? (x) * (y) : G_MAXSIZE)
/**
* gegl_scratch_new: (skip)
* @type: the type of the elements to allocate
* @n: the number of elements to allocate
*
* Allocates @n elements of type @type using scratch memory.
* The returned pointer is cast to a pointer to the given type.
* If @n is 0 it returns %NULL.
* Care is taken to avoid overflow when calculating the size of
* the allocated block.
*
* Since the returned pointer is already cast to the right type,
* it is normally unnecessary to cast it explicitly, and doing
* so might hide memory allocation errors.
*
* Returns: a pointer to the allocated memory, cast to a pointer
* to @type.
*/
#define gegl_scratch_new(type, n) \
((type *) (gegl_scratch_alloc (_GEGL_SCRATCH_MUL (sizeof (type), \
(gsize) (n)))))
/**
* gegl_scratch_new0: (skip)
* @type: the type of the elements to allocate
* @n: the number of elements to allocate
*
* Allocates @n elements of type @type using scratch memory,
* initialized to 0.
* The returned pointer is cast to a pointer to the given type.
* If @n is 0 it returns %NULL.
* Care is taken to avoid overflow when calculating the size of
* the allocated block.
*
* Since the returned pointer is already cast to the right type,
* it is normally unnecessary to cast it explicitly, and doing
* so might hide memory allocation errors.
*
* Returns: a pointer to the allocated memory, cast to a pointer
* to @type.
*/
#define gegl_scratch_new0(type, n) \
((type *) (gegl_scratch_alloc0 (_GEGL_SCRATCH_MUL (sizeof (type), \
(gsize) (n)))))
#endif /* __GEGL_SCRATCH_H__ */
......@@ -23,9 +23,10 @@
#include "gegl.h"
#include "gegl-types-internal.h"
#include "buffer/gegl-buffer-types.h"
#include "buffer/gegl-scratch-private.h"
#include "buffer/gegl-tile-handler-cache.h"
#include "buffer/gegl-tile-handler-zoom.h"
#include "buffer/gegl-tile-backend-swap.h"
#include "buffer/gegl-tile-handler-zoom.h"
#include "gegl-stats.h"
......@@ -48,7 +49,8 @@ enum
PROP_SWAP_READ_TOTAL,
PROP_SWAP_WRITING,
PROP_SWAP_WRITE_TOTAL,
PROP_ZOOM_TOTAL
PROP_ZOOM_TOTAL,
PROP_SCRATCH_TOTAL
};
......@@ -196,6 +198,13 @@ gegl_stats_class_init (GeglStatsClass *klass)
"Total size of data processed by the zoom tile handler",
0, G_MAXUINT64, 0,
G_PARAM_READABLE));
g_object_class_install_property (object_class, PROP_SCRATCH_TOTAL,
g_param_spec_uint64 ("scratch-total",
"Scratch total",
"Total size of scratch memory",
0, G_MAXUINT64, 0,
G_PARAM_READABLE));
}
static void
......@@ -293,6 +302,10 @@ gegl_stats_get_property (GObject *object,
g_value_set_uint64 (value, gegl_tile_handler_zoom_get_total ());
break;
case PROP_SCRATCH_TOTAL:
g_value_set_uint64 (value, gegl_scratch_get_total ());
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment