mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 07:26:15 +01:00
glib/gvariant: Avoid many GBytes allocation
Previously, all GVariants would allocate a GBytes for the buffered contents. This presents a challenge for small GVariant type created during the building process of GVariantBuilder as that results in an allocation for the GVariant, GBytes, and the byte buffer. Recent changes for GBytes may reduce those 3 allocations to 2, but even that is quite substantial overhead for a 32-bit integer. This changeset switches GVariant to use g_new/g_free allocators instead of g_slice_new/free. When benchmarked alone, this presented no measurable difference in overhead with the standard glibc allocator. With that change in place, allocations may then become variable in size to contain small allocations at the end of the GVariant reducing things to a single allocation (plus the GVariantTypeInfo reference). The size of GVariant is already 1 cacheline @ 64-bytes on x86_64. This uses that to guarantee our alignment of data maintains the 8-bytes guarantee of GVariant, and also extends it to match malloc(). On 32-bit systems, we are similarly aligned but reduce the amount we will inline to 32 bytes so we have a total of 1 cacheline. This is all asserted at compile-time to enforce the guarantee. In the end, this changeset reduces the wallclock time of building many GVariant in a loop using GVariantBuilder by 10% beyond the 10% already gained through GBytes doing the same thing.
This commit is contained in:
parent
267342420f
commit
0b083e3d8c
@ -75,8 +75,15 @@ struct _GVariant
|
|||||||
gint state;
|
gint state;
|
||||||
gatomicrefcount ref_count;
|
gatomicrefcount ref_count;
|
||||||
gsize depth;
|
gsize depth;
|
||||||
|
|
||||||
|
guint8 suffix[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Ensure our suffix data aligns to largest guaranteed offset
|
||||||
|
* within GVariant, of 8 bytes.
|
||||||
|
*/
|
||||||
|
G_STATIC_ASSERT (G_STRUCT_OFFSET (GVariant, suffix) % 8 == 0);
|
||||||
|
|
||||||
/* struct GVariant:
|
/* struct GVariant:
|
||||||
*
|
*
|
||||||
* There are two primary forms of GVariant instances: "serialized form"
|
* There are two primary forms of GVariant instances: "serialized form"
|
||||||
@ -543,21 +550,31 @@ g_variant_ensure_serialised (GVariant *value)
|
|||||||
* @type: the type of the new instance
|
* @type: the type of the new instance
|
||||||
* @serialised: if the instance will be in serialised form
|
* @serialised: if the instance will be in serialised form
|
||||||
* @trusted: if the instance will be trusted
|
* @trusted: if the instance will be trusted
|
||||||
|
* @suffix_size: amount of extra bytes to add to allocation
|
||||||
*
|
*
|
||||||
* Allocates a #GVariant instance and does some common work (such as
|
* Allocates a #GVariant instance and does some common work (such as
|
||||||
* looking up and filling in the type info), setting the state field,
|
* looking up and filling in the type info), setting the state field,
|
||||||
* and setting the ref_count to 1.
|
* and setting the ref_count to 1.
|
||||||
*
|
*
|
||||||
|
* Use @suffix_size when you want to store data inside of the GVariant
|
||||||
|
* without having to add an additional GBytes allocation.
|
||||||
|
*
|
||||||
* Returns: a new #GVariant with a floating reference
|
* Returns: a new #GVariant with a floating reference
|
||||||
*/
|
*/
|
||||||
static GVariant *
|
static GVariant *
|
||||||
g_variant_alloc (const GVariantType *type,
|
g_variant_alloc (const GVariantType *type,
|
||||||
gboolean serialised,
|
gboolean serialised,
|
||||||
gboolean trusted)
|
gboolean trusted,
|
||||||
|
gsize suffix_size)
|
||||||
{
|
{
|
||||||
|
G_GNUC_UNUSED gboolean size_check;
|
||||||
GVariant *value;
|
GVariant *value;
|
||||||
|
gsize size;
|
||||||
|
|
||||||
value = g_slice_new (GVariant);
|
size_check = g_size_checked_add (&size, sizeof *value, suffix_size);
|
||||||
|
g_assert (size_check);
|
||||||
|
|
||||||
|
value = g_malloc (size);
|
||||||
value->type_info = g_variant_type_info_get (type);
|
value->type_info = g_variant_type_info_get (type);
|
||||||
value->state = (serialised ? STATE_SERIALISED : 0) |
|
value->state = (serialised ? STATE_SERIALISED : 0) |
|
||||||
(trusted ? STATE_TRUSTED : 0) |
|
(trusted ? STATE_TRUSTED : 0) |
|
||||||
@ -599,6 +616,54 @@ g_variant_new_from_bytes (const GVariantType *type,
|
|||||||
|
|
||||||
/* -- internal -- */
|
/* -- internal -- */
|
||||||
|
|
||||||
|
/* < internal >
|
||||||
|
* g_variant_new_preallocated_trusted:
|
||||||
|
* @data: data to copy
|
||||||
|
* @size: the size of data
|
||||||
|
*
|
||||||
|
* Creates a new #GVariant for simple types such as int32, double, or
|
||||||
|
* bytes.
|
||||||
|
*
|
||||||
|
* Instead of allocating a GBytes, the data will be stored at the tail of
|
||||||
|
* the GVariant structures allocation. This can save considerable malloc
|
||||||
|
* overhead.
|
||||||
|
*
|
||||||
|
* The data is always aligned to the maximum alignment GVariant provides
|
||||||
|
* which is 8 bytes and therefore does not need to verify alignment based
|
||||||
|
* on the the @type provided.
|
||||||
|
*
|
||||||
|
* This should only be used for creating GVariant with trusted data.
|
||||||
|
*
|
||||||
|
* Returns: a new #GVariant with a floating reference
|
||||||
|
*/
|
||||||
|
GVariant *
|
||||||
|
g_variant_new_preallocated_trusted (const GVariantType *type,
|
||||||
|
gconstpointer data,
|
||||||
|
gsize size)
|
||||||
|
{
|
||||||
|
GVariant *value;
|
||||||
|
gsize expected_size;
|
||||||
|
guint alignment;
|
||||||
|
|
||||||
|
value = g_variant_alloc (type, TRUE, TRUE, size);
|
||||||
|
|
||||||
|
g_variant_type_info_query (value->type_info, &alignment, &expected_size);
|
||||||
|
|
||||||
|
g_assert (expected_size == 0 || size == expected_size);
|
||||||
|
|
||||||
|
value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
|
||||||
|
value->contents.serialised.checked_offsets_up_to = G_MAXSIZE;
|
||||||
|
value->contents.serialised.bytes = NULL;
|
||||||
|
value->contents.serialised.data = value->suffix;
|
||||||
|
value->size = size;
|
||||||
|
|
||||||
|
memcpy (value->suffix, data, size);
|
||||||
|
|
||||||
|
TRACE(GLIB_VARIANT_FROM_BUFFER(value, value->type_info, value->ref_count, value->state));
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/* < internal >
|
/* < internal >
|
||||||
* g_variant_new_take_bytes:
|
* g_variant_new_take_bytes:
|
||||||
* @bytes: (transfer full): a #GBytes
|
* @bytes: (transfer full): a #GBytes
|
||||||
@ -620,7 +685,7 @@ g_variant_new_take_bytes (const GVariantType *type,
|
|||||||
GBytes *owned_bytes = NULL;
|
GBytes *owned_bytes = NULL;
|
||||||
GVariantSerialised serialised;
|
GVariantSerialised serialised;
|
||||||
|
|
||||||
value = g_variant_alloc (type, TRUE, trusted);
|
value = g_variant_alloc (type, TRUE, trusted, 0);
|
||||||
|
|
||||||
g_variant_type_info_query (value->type_info,
|
g_variant_type_info_query (value->type_info,
|
||||||
&alignment, &size);
|
&alignment, &size);
|
||||||
@ -728,7 +793,7 @@ g_variant_new_from_children (const GVariantType *type,
|
|||||||
{
|
{
|
||||||
GVariant *value;
|
GVariant *value;
|
||||||
|
|
||||||
value = g_variant_alloc (type, FALSE, trusted);
|
value = g_variant_alloc (type, FALSE, trusted, 0);
|
||||||
value->contents.tree.children = children;
|
value->contents.tree.children = children;
|
||||||
value->contents.tree.n_children = n_children;
|
value->contents.tree.n_children = n_children;
|
||||||
TRACE(GLIB_VARIANT_FROM_CHILDREN(value, value->type_info, value->ref_count, value->state));
|
TRACE(GLIB_VARIANT_FROM_CHILDREN(value, value->type_info, value->ref_count, value->state));
|
||||||
@ -823,7 +888,7 @@ g_variant_unref (GVariant *value)
|
|||||||
g_variant_release_children (value);
|
g_variant_release_children (value);
|
||||||
|
|
||||||
memset (value, 0, sizeof (GVariant));
|
memset (value, 0, sizeof (GVariant));
|
||||||
g_slice_free (GVariant, value);
|
g_free (value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1074,14 +1139,18 @@ g_variant_get_data_as_bytes (GVariant *value)
|
|||||||
{
|
{
|
||||||
const gchar *bytes_data;
|
const gchar *bytes_data;
|
||||||
const gchar *data;
|
const gchar *data;
|
||||||
gsize bytes_size;
|
gsize bytes_size = 0;
|
||||||
gsize size;
|
gsize size;
|
||||||
|
|
||||||
g_variant_lock (value);
|
g_variant_lock (value);
|
||||||
g_variant_ensure_serialised (value);
|
g_variant_ensure_serialised (value);
|
||||||
g_variant_unlock (value);
|
g_variant_unlock (value);
|
||||||
|
|
||||||
|
if (value->contents.serialised.bytes != NULL)
|
||||||
bytes_data = g_bytes_get_data (value->contents.serialised.bytes, &bytes_size);
|
bytes_data = g_bytes_get_data (value->contents.serialised.bytes, &bytes_size);
|
||||||
|
else
|
||||||
|
bytes_data = NULL;
|
||||||
|
|
||||||
data = value->contents.serialised.data;
|
data = value->contents.serialised.data;
|
||||||
size = value->size;
|
size = value->size;
|
||||||
|
|
||||||
@ -1091,11 +1160,13 @@ g_variant_get_data_as_bytes (GVariant *value)
|
|||||||
data = bytes_data;
|
data = bytes_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data == bytes_data && size == bytes_size)
|
if (bytes_data != NULL && data == bytes_data && size == bytes_size)
|
||||||
return g_bytes_ref (value->contents.serialised.bytes);
|
return g_bytes_ref (value->contents.serialised.bytes);
|
||||||
else
|
else if (bytes_data != NULL)
|
||||||
return g_bytes_new_from_bytes (value->contents.serialised.bytes,
|
return g_bytes_new_from_bytes (value->contents.serialised.bytes,
|
||||||
data - bytes_data, size);
|
data - bytes_data, size);
|
||||||
|
else
|
||||||
|
return g_bytes_new (value->contents.serialised.data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1226,7 +1297,7 @@ g_variant_get_child_value (GVariant *value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* create a new serialized instance out of it */
|
/* create a new serialized instance out of it */
|
||||||
child = g_slice_new (GVariant);
|
child = g_new (GVariant, 1);
|
||||||
child->type_info = s_child.type_info;
|
child->type_info = s_child.type_info;
|
||||||
child->state = (value->state & STATE_TRUSTED) |
|
child->state = (value->state & STATE_TRUSTED) |
|
||||||
STATE_SERIALISED;
|
STATE_SERIALISED;
|
||||||
|
@ -25,8 +25,17 @@
|
|||||||
#include <glib/gvariant.h>
|
#include <glib/gvariant.h>
|
||||||
#include <glib/gbytes.h>
|
#include <glib/gbytes.h>
|
||||||
|
|
||||||
|
#if GLIB_SIZEOF_VOID_P == 8
|
||||||
|
# define G_VARIANT_MAX_PREALLOCATED 64
|
||||||
|
#else
|
||||||
|
# define G_VARIANT_MAX_PREALLOCATED 32
|
||||||
|
#endif
|
||||||
|
|
||||||
/* gvariant-core.c */
|
/* gvariant-core.c */
|
||||||
|
|
||||||
|
GVariant * g_variant_new_preallocated_trusted (const GVariantType *type,
|
||||||
|
gconstpointer data,
|
||||||
|
gsize size);
|
||||||
GVariant * g_variant_new_take_bytes (const GVariantType *type,
|
GVariant * g_variant_new_take_bytes (const GVariantType *type,
|
||||||
GBytes *bytes,
|
GBytes *bytes,
|
||||||
gboolean trusted);
|
gboolean trusted);
|
||||||
|
@ -321,6 +321,9 @@ g_variant_new_from_trusted (const GVariantType *type,
|
|||||||
gconstpointer data,
|
gconstpointer data,
|
||||||
gsize size)
|
gsize size)
|
||||||
{
|
{
|
||||||
|
if (size <= G_VARIANT_MAX_PREALLOCATED)
|
||||||
|
return g_variant_new_preallocated_trusted (type, data, size);
|
||||||
|
else
|
||||||
return g_variant_new_take_bytes (type, g_bytes_new (data, size), TRUE);
|
return g_variant_new_take_bytes (type, g_bytes_new (data, size), TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user