glib/gbytes: save small byte buffers inline

When dealing with small allocations it can save considerable cycles to
do a single allocation for both the GBytes and the data by tacking it
onto the end of the GBytes.

Care is taken to preserve the glibc expectation of 2*sizeof(void*)
alignment of allocations at the expense of some padding bytes.

The degenerate case here is when you want to steal the bytes afterwards
but that amounts to the same overhead as the status-quo.

Where this can help considerably is in GVariant building such as
g_variant_new_int32() which allocates for the GVariant, the GBytes, and
the int32 within the GBytes.

In a simple benchmark of using GVariantBuilder to create "(ii)" variants
this saved about 10% in wallclock time.
This commit is contained in:
Christian Hergert 2024-09-25 10:49:19 +00:00 committed by Philip Withnall
parent 84b6f747cb
commit 1e3b010af8
2 changed files with 65 additions and 10 deletions

View File

@ -28,7 +28,6 @@
#include <glib/garray.h>
#include <glib/gstrfuncs.h>
#include <glib/gatomic.h>
#include <glib/gslice.h>
#include <glib/gtestutils.h>
#include <glib/gmem.h>
#include <glib/gmessages.h>
@ -36,6 +35,12 @@
#include <string.h>
#if GLIB_SIZEOF_VOID_P == 8
# define G_BYTES_MAX_INLINE (128 - sizeof(GBytesInline))
#else
# define G_BYTES_MAX_INLINE (64 - sizeof(GBytesInline))
#endif
/**
* GBytes: (copy-func g_bytes_ref) (free-func g_bytes_unref)
*
@ -77,6 +82,21 @@ struct _GBytes
gpointer user_data;
};
typedef struct
{
GBytes bytes;
/* Despite no guarantee about alignment in GBytes, it is nice to
* provide that to ensure that any code which predates support
* for inline data continues to work without disruption. malloc()
* on glibc systems would guarantee 2*sizeof(void*) aligned
* allocations and this matches that.
*/
gsize padding;
guint8 inline_data[];
} GBytesInline;
G_STATIC_ASSERT (G_STRUCT_OFFSET (GBytesInline, inline_data) == (6 * GLIB_SIZEOF_VOID_P));
/**
* g_bytes_new:
* @data: (transfer none) (array length=size) (element-type guint8) (nullable):
@ -87,6 +107,9 @@ struct _GBytes
*
* @data is copied. If @size is 0, @data may be %NULL.
*
* As an optimization, g_bytes_new() may avoid an extra allocation by copying
* the data within the resulting bytes structure if sufficiently small (since GLib 2.84).
*
* Returns: (transfer full): a new #GBytes
*
* Since: 2.32
@ -97,6 +120,22 @@ g_bytes_new (gconstpointer data,
{
g_return_val_if_fail (data != NULL || size == 0, NULL);
if (size <= G_BYTES_MAX_INLINE)
{
GBytesInline *bytes;
bytes = g_malloc (sizeof *bytes + size);
bytes->bytes.data = bytes->inline_data;
bytes->bytes.size = size;
bytes->bytes.free_func = NULL;
bytes->bytes.user_data = NULL;
g_atomic_ref_count_init (&bytes->bytes.ref_count);
memcpy (bytes->inline_data, data, size);
return (GBytes *)bytes;
}
return g_bytes_new_take (g_memdup2 (data, size), size);
}
@ -183,7 +222,7 @@ g_bytes_new_with_free_func (gconstpointer data,
g_return_val_if_fail (data != NULL || size == 0, NULL);
bytes = g_slice_new (GBytes);
bytes = g_new (GBytes, 1);
bytes->data = data;
bytes->size = size;
bytes->free_func = free_func;
@ -335,7 +374,7 @@ g_bytes_unref (GBytes *bytes)
{
if (bytes->free_func != NULL)
bytes->free_func (bytes->user_data);
g_slice_free (GBytes, bytes);
g_free (bytes);
}
}
@ -451,7 +490,7 @@ try_steal_and_unref (GBytes *bytes,
{
*size = bytes->size;
result = (gpointer)bytes->data;
g_slice_free (GBytes, bytes);
g_free (bytes);
return result;
}
@ -469,8 +508,9 @@ try_steal_and_unref (GBytes *bytes,
*
* As an optimization, the byte data is returned without copying if this was
* the last reference to bytes and bytes was created with g_bytes_new(),
* g_bytes_new_take() or g_byte_array_free_to_bytes(). In all other cases the
* data is copied.
* g_bytes_new_take() or g_byte_array_free_to_bytes() and the buffer was larger
* than the size #GBytes may internalize within its allocation. In all other
* cases the data is copied.
*
* Returns: (transfer full) (array length=size) (element-type guint8)
* (not nullable): a pointer to the same byte data, which should be
@ -516,8 +556,9 @@ g_bytes_unref_to_data (GBytes *bytes,
*
* As an optimization, the byte data is transferred to the array without copying
* if this was the last reference to bytes and bytes was created with
* g_bytes_new(), g_bytes_new_take() or g_byte_array_free_to_bytes(). In all
* other cases the data is copied.
* g_bytes_new(), g_bytes_new_take() or g_byte_array_free_to_bytes() and the
* buffer was larger than the size #GBytes may internalize within its allocation.
* In all other cases the data is copied.
*
* Do not use it if @bytes contains more than %G_MAXUINT
* bytes. #GByteArray stores the length of its data in #guint, which

View File

@ -28,8 +28,22 @@ struct _GBytes
gpointer user_data;
};
static const gchar *NYAN = "nyannyan";
static const gsize N_NYAN = 8;
static const gchar NYAN[128] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127
};
#define N_NYAN sizeof(NYAN)
static void
test_new (void)