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/garray.h>
#include <glib/gstrfuncs.h> #include <glib/gstrfuncs.h>
#include <glib/gatomic.h> #include <glib/gatomic.h>
#include <glib/gslice.h>
#include <glib/gtestutils.h> #include <glib/gtestutils.h>
#include <glib/gmem.h> #include <glib/gmem.h>
#include <glib/gmessages.h> #include <glib/gmessages.h>
@ -36,6 +35,12 @@
#include <string.h> #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) * GBytes: (copy-func g_bytes_ref) (free-func g_bytes_unref)
* *
@ -77,6 +82,21 @@ struct _GBytes
gpointer user_data; 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: * g_bytes_new:
* @data: (transfer none) (array length=size) (element-type guint8) (nullable): * @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. * @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 * Returns: (transfer full): a new #GBytes
* *
* Since: 2.32 * Since: 2.32
@ -97,6 +120,22 @@ g_bytes_new (gconstpointer data,
{ {
g_return_val_if_fail (data != NULL || size == 0, NULL); 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); 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); g_return_val_if_fail (data != NULL || size == 0, NULL);
bytes = g_slice_new (GBytes); bytes = g_new (GBytes, 1);
bytes->data = data; bytes->data = data;
bytes->size = size; bytes->size = size;
bytes->free_func = free_func; bytes->free_func = free_func;
@ -335,7 +374,7 @@ g_bytes_unref (GBytes *bytes)
{ {
if (bytes->free_func != NULL) if (bytes->free_func != NULL)
bytes->free_func (bytes->user_data); 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; *size = bytes->size;
result = (gpointer)bytes->data; result = (gpointer)bytes->data;
g_slice_free (GBytes, bytes); g_free (bytes);
return result; 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 * 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(), * 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 * g_bytes_new_take() or g_byte_array_free_to_bytes() and the buffer was larger
* data is copied. * 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) * Returns: (transfer full) (array length=size) (element-type guint8)
* (not nullable): a pointer to the same byte data, which should be * (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 * 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 * 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 * g_bytes_new(), g_bytes_new_take() or g_byte_array_free_to_bytes() and the
* other cases the data is copied. * 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 * Do not use it if @bytes contains more than %G_MAXUINT
* bytes. #GByteArray stores the length of its data in #guint, which * bytes. #GByteArray stores the length of its data in #guint, which

View File

@ -28,8 +28,22 @@ struct _GBytes
gpointer user_data; gpointer user_data;
}; };
static const gchar *NYAN = "nyannyan"; static const gchar NYAN[128] = {
static const gsize N_NYAN = 8; 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 static void
test_new (void) test_new (void)