GVariant: calculate size at construction

It's always possible to determine the serialised size of a GVariant
instance, even in the case that it is not yet serialised.  This can be
done by calling g_variant_get_size() which will base its answer on the
size of each child (which must be recursively determined).

We must perform this process before we can allocate the buffer to
serialise a GVariant into (since we must know the size of the buffer).
This means that serialising a GVariant involves two steps that recurse
through the entire tree of values.  We must take locks twice.

Simplify this by always determining the size when the instance is first
created, from the sizes of its children (which now will always be known
as well).  We can do this without taking any locks because the
newly-created instance has never been exposed and because the size on
the children is now a constant that can be directly accessed without a
lock.

This is a reduction in complexity and will also be a performance
improvement in all cases where a GVariant is serialised.  It will be a
slight performance hit in the case that we construct tree-form instances
and never serialise them.
This commit is contained in:
Ryan Lortie
2014-11-27 16:37:13 -05:00
parent f2437b7f1c
commit 67520c60fa

View File

@@ -106,21 +106,11 @@ struct _GVariant
* The type_info field never changes during the life of the * The type_info field never changes during the life of the
* instance, so it can be accessed without a lock. * instance, so it can be accessed without a lock.
* *
* size: this is the size of the serialised form for the instance, if it * size: this is the size of the serialised form for the instance. It
* is known. If the instance is in serialised form then it is, by * is known for serialised instances and also tree-form instances
* definition, known. If the instance is in tree form then it may * (for which it is calculated at construction time, from the
* be unknown (in which case it is -1). It is possible for the * known sizes of the children used). After construction, it
* size to be known when in tree form if, for example, the user * never changes and therefore can be accessed without a lock.
* has called g_variant_get_size() without calling
* g_variant_get_data(). Additionally, even when the user calls
* g_variant_get_data() the size of the data must first be
* determined so that a large enough buffer can be allocated for
* the data.
*
* Once the size is known, it can never become unknown again.
* g_variant_ensure_size() is used to ensure that the size is in
* the known state -- it calculates the size if needed. After
* that, the size field can be accessed without a lock.
* *
* contents: a union containing either the information associated with * contents: a union containing either the information associated with
* holding a value in serialised form or holding a value in * holding a value in serialised form or holding a value in
@@ -276,31 +266,19 @@ g_variant_release_children (GVariant *value)
* instances are always in serialised form. For these instances, * instances are always in serialised form. For these instances,
* storing their serialised form merely involves a memcpy(). * storing their serialised form merely involves a memcpy().
* *
* Serialisation is a two-step process. First, the size of the * Converting to serialised form:
* serialised data must be calculated so that an appropriately-sized
* buffer can be allocated. Second, the data is written into the
* buffer.
* *
* Determining the size: * The first step in the process of converting a GVariant to
* The process of determining the size is triggered by a call to * serialised form is to allocate a buffer. The size of the buffer is
* g_variant_ensure_size() on a container. This invokes the * always known because we computed at construction time of the
* serialiser code to determine the size. The serialiser is passed * GVariant.
* g_variant_fill_gvs() as a callback.
* *
* g_variant_fill_gvs() is called by the serialiser on each child of
* the container which, in turn, calls g_variant_ensure_size() on
* itself and fills in the result of its own size calculation.
*
* The serialiser uses the size information from the children to
* calculate the size needed for the entire container.
*
* Writing the data:
* After the buffer has been allocated, g_variant_serialise() is * After the buffer has been allocated, g_variant_serialise() is
* called on the container. This invokes the serialiser code to write * called on the container. This invokes the serialiser code to write
* the bytes to the container. The serialiser is, again, passed * the bytes to the container. The serialiser is passed
* g_variant_fill_gvs() as a callback. * g_variant_fill_gvs() as a callback.
* *
* This time, when g_variant_fill_gvs() is called for each child, the * At the time that g_variant_fill_gvs() is called for each child, the
* child is given a pointer to a sub-region of the allocated buffer * child is given a pointer to a sub-region of the allocated buffer
* where it should write its data. This is done by calling * where it should write its data. This is done by calling
* g_variant_store(). In the event that the instance is in serialised * g_variant_store(). In the event that the instance is in serialised
@@ -313,34 +291,6 @@ g_variant_release_children (GVariant *value)
*/ */
static void g_variant_fill_gvs (GVariantSerialised *, gpointer); static void g_variant_fill_gvs (GVariantSerialised *, gpointer);
/* < private >
* g_variant_ensure_size:
* @value: a #GVariant
*
* Ensures that the ->size field of @value is filled in properly. This
* must be done as a precursor to any serialisation of the value in
* order to know how large of a buffer is needed to store the data.
*
* The current thread must hold the lock on @value.
*/
static void
g_variant_ensure_size (GVariant *value)
{
g_assert (value->state & STATE_LOCKED);
if (value->size == (gssize) -1)
{
gpointer *children;
gsize n_children;
children = (gpointer *) value->contents.tree.children;
n_children = value->contents.tree.n_children;
value->size = g_variant_serialiser_needed_size (value->type_info,
g_variant_fill_gvs,
children, n_children);
}
}
/* < private > /* < private >
* g_variant_serialise: * g_variant_serialise:
* @value: a #GVariant * @value: a #GVariant
@@ -386,9 +336,12 @@ g_variant_serialise (GVariant *value,
* *
* - reporting its type * - reporting its type
* *
* - reporting its serialised size (requires knowing the size first) * - reporting its serialised size
* *
* - possibly storing its serialised form into the provided buffer * - possibly storing its serialised form into the provided buffer
*
* This callback is also used during g_variant_new_from_children() in
* order to discover the size and type of each child.
*/ */
static void static void
g_variant_fill_gvs (GVariantSerialised *serialised, g_variant_fill_gvs (GVariantSerialised *serialised,
@@ -396,10 +349,6 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
{ {
GVariant *value = data; GVariant *value = data;
g_variant_lock (value);
g_variant_ensure_size (value);
g_variant_unlock (value);
if (serialised->type_info == NULL) if (serialised->type_info == NULL)
serialised->type_info = value->type_info; serialised->type_info = value->type_info;
g_assert (serialised->type_info == value->type_info); g_assert (serialised->type_info == value->type_info);
@@ -423,11 +372,10 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
* *
* Ensures that @value is in serialised form. * Ensures that @value is in serialised form.
* *
* If @value is in tree form then this function ensures that the * If @value is in tree form then this function allocates a buffer of
* serialised size is known and then allocates a buffer of that size and * that size and serialises the instance into the buffer. The
* serialises the instance into the buffer. The 'children' array is * 'children' array is then released and the instance is set to
* then released and the instance is set to serialised form based on the * serialised form based on the contents of the buffer.
* contents of the buffer.
* *
* The current thread must hold the lock on @value. * The current thread must hold the lock on @value.
*/ */
@@ -441,7 +389,6 @@ g_variant_ensure_serialised (GVariant *value)
GBytes *bytes; GBytes *bytes;
gpointer data; gpointer data;
g_variant_ensure_size (value);
data = g_malloc (value->size); data = g_malloc (value->size);
g_variant_serialise (value, data); g_variant_serialise (value, data);
@@ -478,7 +425,6 @@ g_variant_alloc (const GVariantType *type,
value->state = (serialised ? STATE_SERIALISED : 0) | value->state = (serialised ? STATE_SERIALISED : 0) |
(trusted ? STATE_TRUSTED : 0) | (trusted ? STATE_TRUSTED : 0) |
STATE_FLOATING; STATE_FLOATING;
value->size = (gssize) -1;
value->ref_count = 1; value->ref_count = 1;
return value; return value;
@@ -565,6 +511,8 @@ g_variant_new_from_children (const GVariantType *type,
value = g_variant_alloc (type, FALSE, trusted); value = g_variant_alloc (type, FALSE, trusted);
value->contents.tree.children = children; value->contents.tree.children = children;
value->contents.tree.n_children = n_children; value->contents.tree.n_children = n_children;
value->size = g_variant_serialiser_needed_size (value->type_info, g_variant_fill_gvs,
(gpointer *) children, n_children);
return value; return value;
} }
@@ -813,10 +761,6 @@ g_variant_is_floating (GVariant *value)
gsize gsize
g_variant_get_size (GVariant *value) g_variant_get_size (GVariant *value)
{ {
g_variant_lock (value);
g_variant_ensure_size (value);
g_variant_unlock (value);
return value->size; return value->size;
} }