On 16 Sep 2024, at 18:33, Grigorii Nazarov wrote:

> `Hi Eelco,
>
> See my comments below.
>
> I'm new to this process, so I guess I'm to address all the issues together 
> when we agree on currently mentioned?

You are right Grigorii. After we wrap up the discussion on all patches in the 
series you can send out a new revision, which will go through another round of 
reviews.

If something is not clear, just let me know.

See my comments below…


> On 9/6/24 5:14 PM, Eelco Chaudron wrote:
>>
>>
>> On 1 Jul 2024, at 13:02, Grigorii Nazarov wrote:
>>
>> Hi Grigorii,
>>
>> This is not an in-depth review, but find some comments below.
>>
>> Cheers,
>>
>> Eelco
>>
>>> Currently serialization is performed by first converting
>>> the internal data representation into JSON objects, followed by
>>> serializing these objects by jsonrpc. This process results in lots of
>>> allocations for these intermediate objects. Consequently, this
>>> not only increases peak memory consumption, but also
>>> demands significantly more CPU work.
>>>
>>> By forming row-update JSONs directly in `ds`, which is then used
>>> to create 'serialized object' JSONs, the overall speed increased
>>> by a factor of 2.3.
>>>
>>> A local benchmark was run on a proprietary Southbound backup.
>>> Both versions, before and after applying the patch, were measured.
>>> For each version, there were two runs with 10 parallel clients,
>>> and two runs with 30 parallel clients. CPU time was recorded
>>> after startup (before clients started running) and after
>>> all clients received all updates. Clients were essentially running
>>> `ovsdb-client monitor-cond unix:pipe OVN_Southbound
>>> '[[\"actions\",\"!=\",\"sdfdfsd\"]]' Logical_Flow`.
>>> Similar results were obtained with other requests
>>> that required a significant amount of data transfer.
>>> The backup size is about 600 MB. Results are measured in seconds.
>>>
>>>                  Before  After
>>> Baseline x10:   9.53    108.54
>>> Baseline x10:   9.62    108.67
>>> Baseline x30:   9.69    307.04
>>> Baseline x30:   9.65    303.32
>>> Patch x10:      9.67    52.57
>>> Patch x10:      9.57    53.12
>>> Patch x30:      9.53    136.33
>>> Patch x30:      9.63    135.88
>>
>> Are before and after times reversed? I assume a shorter time is better?
>> Also, how does this relate to the 2.3x speed improvement?
>>
>
> Honestly, it took me some time too, so probably I should reformat it somehow. 
> Meanwhile, 'before' and 'after' meaning is before and after payload, so the 
> time we improve is (After - Before).
>
> So, we have something close to
> Baseline x10 = 99
> Baseline x30 = 294
> Patch x10 = 43
> Patch x30 = 137
>
> Which is about 2.1-2.3 times greater for Baseline vs Patch.
> I had more thorough testing locally, including wall clock, but I think it's 
> lost by now.

Now I get it :) And yes you might want to reformat it.

>>> Signed-off-by: Grigorii Nazarov <whitecrow...@gmail.com>
>>> ---
>>> v2: updated title
>>> v3: fixed bracing
>>> v4: changed patch number from 4/4 to 3/3
>>>
>>>   include/openvswitch/json.h |   1 +
>>>   lib/json.c                 |   8 ++
>>>   lib/ovsdb-data.c           | 105 +++++++++++++++++++
>>>   lib/ovsdb-data.h           |   3 +
>>>   ovsdb/monitor.c            | 210 ++++++++++++++++++++++++-------------
>>>   5 files changed, 254 insertions(+), 73 deletions(-)
>>>
>>> diff --git a/include/openvswitch/json.h b/include/openvswitch/json.h
>>> index 555440760..80b9479c7 100644
>>> --- a/include/openvswitch/json.h
>>> +++ b/include/openvswitch/json.h
>>> @@ -81,6 +81,7 @@ struct json *json_boolean_create(bool);
>>>   struct json *json_string_create(const char *);
>>>   struct json *json_string_create_nocopy(char *);
>>>   struct json *json_serialized_object_create(const struct json *);
>>> +struct json *json_serialized_object_create_from_string(const char *);
>>>   struct json *json_integer_create(long long int);
>>>   struct json *json_real_create(double);
>>>
>>> diff --git a/lib/json.c b/lib/json.c
>>> index d40e93857..66b1f571f 100644
>>> --- a/lib/json.c
>>> +++ b/lib/json.c
>>> @@ -199,6 +199,14 @@ json_serialized_object_create(const struct json *src)
>>>       return json;
>>>   }
>>>
>>> +struct json *
>>> +json_serialized_object_create_from_string(const char *s)
>>> +{
>>> +    struct json *json = json_create(JSON_SERIALIZED_OBJECT);
>>> +    json->string = xstrdup(s);
>>> +    return json;
>>> +}
>>> +
>>>   struct json *
>>>   json_serialized_object_create_with_yield(const struct json *src)
>>>   {
>>> diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
>>> index defb048d7..f32b7975a 100644
>>> --- a/lib/ovsdb-data.c
>>> +++ b/lib/ovsdb-data.c
>>> @@ -492,6 +492,45 @@ ovsdb_atom_to_json__(const union ovsdb_atom *atom, 
>>> enum ovsdb_atomic_type type,
>>>       }
>>>   }
>>>
>>> +/* Serializes 'atom', of the specified 'type', into 'ds' in JSON format.
>>> + *
>>> + * Refer to RFC 7047 for the format of the JSON that this function 
>>> produces. */
>>> +static void
>>> +ovsdb_atom_to_json_ds(const union ovsdb_atom *atom,
>>> +                      enum ovsdb_atomic_type type, struct ds *ds)
>>
>> Isn’t this a duplicate of ovsdb_atom_to_string()?
>>
>
> This is ovsdb_atom_to_json__ with inlined json creation. Differences from 
> ovsdb_atom_to_string are small and are mostly about how UUID is serialized.

Maybe do something like the below to avoid duplication, and use it in both 
functions.

static void
ovsdb_atom_to_json_ds__(const union ovsdb_atom *atom,
                        enum ovsdb_atomic_type type, struct ds *ds,
                        bool forced_string_quotes, bool bracket_uuid)
{
…
}

>>> +{
>>> +    switch (type) {
>>> +    case OVSDB_TYPE_VOID:
>>> +        OVS_NOT_REACHED();
>>> +
>>> +    case OVSDB_TYPE_INTEGER:
>>> +        ds_put_format(ds, "%lld", (long long) atom->integer);
>>
>> Rather than typecast this one, just use:
>>
>>   ds_put_format(ds, "%"PRId64, atom->integer);
>>
>
> Agreed.
>
>>> +        return;
>>> +
>>> +    case OVSDB_TYPE_REAL:
>>> +        ds_put_format(ds, "%.*g", DBL_DIG, atom->real);
>>> +        return;
>>> +
>>> +    case OVSDB_TYPE_BOOLEAN:
>>> +        ds_put_cstr(ds, atom->boolean ? "true" : "false");
>>> +        return;
>>> +
>>> +    case OVSDB_TYPE_STRING:
>>> +        json_to_ds(atom->s, JSSF_SORT, ds);
>>> +        return;
>>> +
>>> +    case OVSDB_TYPE_UUID:
>>> +        ds_put_cstr(ds, "[\"uuid\",\"");
>>> +        ds_put_format(ds, UUID_FMT, UUID_ARGS(&atom->uuid));
>>> +        ds_put_cstr(ds, "\"]");
>>> +        return;
>>> +
>>> +    case OVSDB_N_TYPES:
>>> +    default:
>>> +        OVS_NOT_REACHED();
>>> +    }
>>> +}
>>> +
>>>   struct json *
>>>   ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type 
>>> type)
>>>   {
>>> @@ -1445,6 +1484,23 @@ ovsdb_base_to_json(const union ovsdb_atom *atom,
>>>       }
>>>   }
>>>
>>> +static void
>>> +ovsdb_base_to_json_ds(const union ovsdb_atom *atom,
>>> +                      const struct ovsdb_base_type *base,
>>> +                      bool use_row_names,
>>> +                      struct ds *ds)
>>> +{
>>
>> Not sure what all this logic is trying to do, as it’s not
>> straightforward. If I reverse the logic it could be the following:
>>
>> if (base->type == OVSDB_TYPE_UUID) {
>>
>> } else {
>>
>> }
>>
>> But I’m not sure this is what you want.
>
> This is but ovsdb_base_to_json with json building made in place. I'm not sure 
> why is it done this way too. As to reversing logic, the correct way might be
> if (use_row_names
>     && base->type == OVSDB_TYPE_UUID
>     && base->uuid.refTableName) {
>       // write named-uuid
> } else {
>       // atom to json
> }
>

You are right, I might not had enough coffee when reviewing this the first time 
:(

Looking at this (and the below) again, I see it’s only ever called with 
use_row_names = false. We should probably optimize this all out.
So ovsdb_datum_to_json_ds__() would become ovsdb_datum_to_json_ds() with the 
bool use_row_names parameter removed.


>>> +    if (!use_row_names
>>> +        || base->type != OVSDB_TYPE_UUID
>>> +        || !base->uuid.refTableName) {
>>> +        ovsdb_atom_to_json_ds(atom, base->type, ds);
>>> +    } else {
>>> +        ds_put_cstr(ds, "[\"named-uuid\",\"");
>>> +        ds_put_format(ds, UUID_ROW_FMT, UUID_ARGS(&atom->uuid));
>>> +        ds_put_cstr(ds, "\"]");
>>> +    }
>>> +}
>>> +
>>>   static struct json *
>>>   ovsdb_datum_to_json__(const struct ovsdb_datum *datum,
>>>                         const struct ovsdb_type *type,
>>> @@ -1482,6 +1538,47 @@ ovsdb_datum_to_json__(const struct ovsdb_datum 
>>> *datum,
>>>       }
>>>   }
>>>
>>> +static void
>>> +ovsdb_datum_to_json_ds__(const struct ovsdb_datum *datum,
>>> +                         const struct ovsdb_type *type,
>>> +                         bool use_row_names,
>>> +                         struct ds *ds)
>>> +{
>>> +    if (ovsdb_type_is_map(type)) {
>>> +        size_t i;
>>> +
>>> +        ds_put_cstr(ds, "[\"map\",[");
>>> +        for (i = 0; i < datum->n; i++) {
>>> +            if (i != 0) {
>>> +                ds_put_char(ds, ',');
>>> +            }
>>> +            ds_put_char(ds, '[');
>>> +            ovsdb_base_to_json_ds(&datum->keys[i], &type->key,
>>> +                                  use_row_names, ds);
>>> +            ds_put_char(ds, ',');
>>> +            ovsdb_base_to_json_ds(&datum->values[i], &type->value,
>>> +                                  use_row_names, ds);
>>> +            ds_put_char(ds, ']');
>>> +        }
>>> +        ds_put_cstr(ds, "]]");
>>> +    } else if (datum->n == 1) {
>>> +        ovsdb_base_to_json_ds(&datum->keys[0], &type->key,
>>> +                              use_row_names, ds);
>>> +    } else {
>>> +        size_t i;
>>> +
>>> +        ds_put_cstr(ds, "[\"set\",[");
>>> +        for (i = 0; i < datum->n; i++) {
>>> +            if (i != 0) {
>>> +                ds_put_char(ds, ',');
>>> +            }
>>> +            ovsdb_base_to_json_ds(&datum->keys[i], &type->key,
>>> +                                  use_row_names, ds);
>>> +        }
>>> +        ds_put_cstr(ds, "]]");
>>> +    }
>>> +}
>>> +
>>>   /* Converts 'datum', of the specified 'type', to JSON format, and returns 
>>> the
>>>    * JSON.  The caller is responsible for freeing the returned JSON.
>>>    *
>>> @@ -1509,6 +1606,14 @@ ovsdb_datum_to_json_with_row_names(const struct 
>>> ovsdb_datum *datum,
>>>       return ovsdb_datum_to_json__(datum, type, true, true);
>>>   }
>>>
>>> +void
>>> +ovsdb_datum_to_json_ds(const struct ovsdb_datum *datum,
>>> +                       const struct ovsdb_type *type,
>>> +                       struct ds *ds)
>>> +{
>>> +    ovsdb_datum_to_json_ds__(datum, type, false, ds);
>>> +}
>>> +
>>>   static const char *
>>>   skip_spaces(const char *p)
>>>   {
>>> diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
>>> index c0408ee49..5bc47f0a1 100644
>>> --- a/lib/ovsdb-data.h
>>> +++ b/lib/ovsdb-data.h
>>> @@ -197,6 +197,9 @@ struct json *ovsdb_datum_to_json(const struct 
>>> ovsdb_datum *,
>>>                                    const struct ovsdb_type *);
>>>   struct json *ovsdb_datum_to_json_deep(const struct ovsdb_datum *,
>>>                                         const struct ovsdb_type *);
>>> +void ovsdb_datum_to_json_ds(const struct ovsdb_datum *,
>>> +                            const struct ovsdb_type *,
>>> +                            struct ds *);
>>>
>>>   char *ovsdb_datum_from_string(struct ovsdb_datum *,
>>>                                 const struct ovsdb_type *, const char *,
>>> diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
>>> index c3bfae3d2..8355b8da7 100644
>>> --- a/ovsdb/monitor.c
>>> +++ b/ovsdb/monitor.c
>>> @@ -25,6 +25,7 @@
>>>   #include "openvswitch/json.h"
>>>   #include "json.h"
>>>   #include "jsonrpc.h"
>>> +#include "ovsdb-data.h"
>>>   #include "ovsdb-error.h"
>>>   #include "ovsdb-parser.h"
>>>   #include "ovsdb.h"
>>> @@ -197,14 +198,15 @@ enum ovsdb_monitor_row_type {
>>>       OVSDB_MONITOR_ROW
>>>   };
>>>
>>> -typedef struct json *
>>> +typedef bool
>>>   (*compose_row_update_cb_func)
>>>       (const struct ovsdb_monitor_table *mt,
>>>        const struct ovsdb_monitor_session_condition * condition,
>>>        enum ovsdb_monitor_row_type row_type,
>>>        const void *,
>>>        bool initial, unsigned long int *changed,
>>> -     size_t n_columns);
>>> +     size_t n_columns,
>>> +     struct ds *);
>>>
>>>   static void ovsdb_monitor_destroy(struct ovsdb_monitor *);
>>>   static struct ovsdb_monitor_change_set * ovsdb_monitor_add_change_set(
>>> @@ -963,28 +965,27 @@ ovsdb_monitor_row_skip_update(const struct 
>>> ovsdb_monitor_table *mt,
>>>       return false;
>>>   }
>>>
>>> -/* Returns JSON for a <row-update> (as described in RFC 7047) for 'row' 
>>> within
>>> - * 'mt', or NULL if no row update should be sent.
>>> +/* Serializes a <row-update> (as described in RFC 7047) for 'row' within
>>> + * 'mt' into 'ds'. Returns true unless no row update should be sent.
>>>    *
>>> - * The caller should specify 'initial' as true if the returned JSON is 
>>> going to
>>> - * be used as part of the initial reply to a "monitor" request, false if 
>>> it is
>>> - * going to be used as part of an "update" notification.
>>> + * The caller should specify 'initial' as true if the serialized JSON is
>>> + * going to be used as part of the initial reply to a "monitor" request,
>>> + * false if it is going to be used as part of an "update" notification.
>>>    *
>>>    * 'changed' must be a scratch buffer for internal use that is at least
>>>    * bitmap_n_bytes(n_columns) bytes long. */
>>> -static struct json *
>>> +static bool
>>>   ovsdb_monitor_compose_row_update(
>>>       const struct ovsdb_monitor_table *mt,
>>>       const struct ovsdb_monitor_session_condition *condition OVS_UNUSED,
>>>       enum ovsdb_monitor_row_type row_type OVS_UNUSED,
>>>       const void *_row,
>>>       bool initial, unsigned long int *changed,
>>> -    size_t n_columns OVS_UNUSED)
>>> +    size_t n_columns OVS_UNUSED,
>>> +    struct ds *ds)
>>>   {
>>>       const struct ovsdb_monitor_row *row = _row;
>>>       enum ovsdb_monitor_selection type;
>>> -    struct json *old_json, *new_json;
>>> -    struct json *row_json;
>>>       size_t i;
>>>
>>>       ovs_assert(row_type == OVSDB_MONITOR_ROW);
>>> @@ -992,65 +993,95 @@ ovsdb_monitor_compose_row_update(
>>>       if (ovsdb_monitor_row_skip_update(mt, row_type, row->old,
>>>                                         row->new, type, changed,
>>>                                         mt->n_columns)) {
>>> -        return NULL;
>>> +        return false;
>>>       }
>>>
>>> -    row_json = json_object_create();
>>> -    old_json = new_json = NULL;
>>> +    ds_put_char(ds, '{');
>>> +
>>> +    bool has_old = false;
>>>       if (type & (OJMS_DELETE | OJMS_MODIFY)) {
>>> -        old_json = json_object_create();
>>> -        json_object_put(row_json, "old", old_json);
>>> -    }
>>> -    if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
>>> -        new_json = json_object_create();
>>> -        json_object_put(row_json, "new", new_json);
>>> -    }
>>> -    for (i = 0; i < mt->n_monitored_columns; i++) {
>>> -        const struct ovsdb_monitor_column *c = &mt->columns[i];
>>> +        ds_put_cstr(ds, "\"old\":{");
>>>
>>
>> There is now a lot of duplicate code with only the old/new string being 
>> different. Can we create a function to remove the duplication?
>>
>
> Agreed. I'll address it in the next revision.
>
>>> -        if (!c->monitored || !(type & c->select))  {
>>> -            /* We don't care about this type of change for this
>>> -             * particular column (but we will care about it for some
>>> -             * other column). */
>>> -            continue;
>>> +        has_old = true;
>>> +        bool empty = true;
>>> +        for (i = 0; i < mt->n_monitored_columns; i++) {
>>> +            const struct ovsdb_monitor_column *c = &mt->columns[i];
>>> +
>>> +            if (!c->monitored || !(type & c->select)) {
>>> +                /* We don't care about this type of change for this
>>> +                * particular column (but we will care about it for some
>>> +                * other column). */
>>> +                continue;
>>> +            }
>>> +
>>> +            if (type == OJMS_DELETE || bitmap_is_set(changed, i)) {
>>> +                if (!empty) {
>>> +                    ds_put_char(ds, ',');
>>> +                }
>>> +                empty = false;
>>> +
>>> +                json_string_escape(c->column->name, ds);
>>> +                ds_put_char(ds, ':');
>>> +                ovsdb_datum_to_json_ds(&row->old[i], &c->column->type, ds);
>>> +            }
>>>           }
>>> +        ds_put_char(ds, '}');
>>> +    }
>>>
>>> -        if ((type == OJMS_MODIFY && bitmap_is_set(changed, i))
>>> -            || type == OJMS_DELETE) {
>>> -            json_object_put(old_json, c->column->name,
>>> -                            ovsdb_datum_to_json(&row->old[i],
>>> -                                                &c->column->type));
>>> +    if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
>>> +        if (has_old) {
>>> +            ds_put_char(ds, ',');
>>>           }
>>> -        if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
>>> -            json_object_put(new_json, c->column->name,
>>> -                            ovsdb_datum_to_json(&row->new[i],
>>> -                                                &c->column->type));
>>> +        ds_put_cstr(ds, "\"new\":{");
>>> +
>>> +        bool empty = true;
>>> +        for (i = 0; i < mt->n_monitored_columns; i++) {
>>> +            const struct ovsdb_monitor_column *c = &mt->columns[i];
>>> +
>>> +            if (!c->monitored || !(type & c->select)) {
>>> +                /* We don't care about this type of change for this
>>> +                * particular column (but we will care about it for some
>>> +                * other column). */
>>> +                continue;
>>> +            }
>>> +
>>> +            if (!empty) {
>>> +                ds_put_char(ds, ',');
>>> +            }
>>> +            empty = false;
>>> +
>>> +            json_string_escape(c->column->name, ds);
>>> +            ds_put_char(ds, ':');
>>> +            ovsdb_datum_to_json_ds(&row->new[i], &c->column->type, ds);
>>>           }
>>> +        ds_put_char(ds, '}');
>>>       }
>>> +    ds_put_char(ds, '}');
>>>
>>> -    return row_json;
>>> +    return true;
>>>   }
>>>
>>> -/* Returns JSON for a <row-update2> (as described in ovsdb-server(1) 
>>> mapage)
>>> - * for 'row' within * 'mt', or NULL if no row update should be sent.
>>> +/* Serializes a <row-update2> (as described in ovsdb-server(1) mapage)
>>> + * for 'row' within * 'mt' into 'ds'.
>>> + * Returns true unless no row update should be sent.
>>>    *
>>> - * The caller should specify 'initial' as true if the returned JSON is
>>> + * The caller should specify 'initial' as true if the serialized JSON is
>>>    * going to be used as part of the initial reply to a "monitor_cond" 
>>> request,
>>>    * false if it is going to be used as part of an "update2" notification.
>>>    *
>>>    * 'changed' must be a scratch buffer for internal use that is at least
>>>    * bitmap_n_bytes(n_columns) bytes long. */
>>> -static struct json *
>>> +static bool
>>>   ovsdb_monitor_compose_row_update2(
>>>       const struct ovsdb_monitor_table *mt,
>>>       const struct ovsdb_monitor_session_condition *condition,
>>>       enum ovsdb_monitor_row_type row_type,
>>>       const void *_row,
>>>       bool initial, unsigned long int *changed,
>>> -    size_t n_columns)
>>> +    size_t n_columns,
>>> +    struct ds *ds)
>>>   {
>>>       enum ovsdb_monitor_selection type;
>>> -    struct json *row_update2, *diff_json;
>>>       const struct ovsdb_datum *old, *new;
>>>       size_t i;
>>>
>>> @@ -1065,15 +1096,18 @@ ovsdb_monitor_compose_row_update2(
>>>                                                      row_type, old, new);
>>>       if (ovsdb_monitor_row_skip_update(mt, row_type, old, new, type, 
>>> changed,
>>>                                         n_columns)) {
>>> -        return NULL;
>>> +        return false;
>>>       }
>>>
>>> -    row_update2 = json_object_create();
>>> +    ds_put_char(ds, '{');
>>>       if (type == OJMS_DELETE) {
>>> -        json_object_put(row_update2, "delete", json_null_create());
>>> +        ds_put_cstr(ds, "\"delete\":null");
>>>       } else {
>>> -        diff_json = json_object_create();
>>>           const char *op;
>>> +        op = type == OJMS_INITIAL ? "initial"
>>> +                                  : type == OJMS_MODIFY ? "modify" : 
>>> "insert";
>>> +        ds_put_format(ds, "\"%s\":{", op);
>>> +        bool empty = true;
>>>
>>>           for (i = 0; i < mt->n_monitored_columns; i++) {
>>>               const struct ovsdb_monitor_column *c = &mt->columns[i];
>>> @@ -1085,33 +1119,43 @@ ovsdb_monitor_compose_row_update2(
>>>                   continue;
>>>               }
>>>
>>> -            if (type == OJMS_MODIFY) {
>>> -                struct ovsdb_datum diff;
>>> +            struct ovsdb_datum diff;
>>> +            const struct ovsdb_datum *column_update = NULL;
>>>
>>> +            if (type == OJMS_MODIFY) {
>>>                   if (!bitmap_is_set(changed, i)) {
>>>                       continue;
>>>                   }
>>>
>>> -                ovsdb_datum_diff(&diff ,&old[index], &new[index],
>>> +                ovsdb_datum_diff(&diff, &old[index], &new[index],
>>>                                           &c->column->type);
>>> -                json_object_put(diff_json, c->column->name,
>>> -                                ovsdb_datum_to_json(&diff, 
>>> &c->column->type));
>>> -                ovsdb_datum_destroy(&diff, &c->column->type);
>>> +                column_update = &diff;
>>>               } else {
>>> -                if (!ovsdb_datum_is_default(&new[index], 
>>> &c->column->type)) {
>>> -                    json_object_put(diff_json, c->column->name,
>>> -                                    ovsdb_datum_to_json(&new[index],
>>> -                                                        &c->column->type));
>>> +                if (ovsdb_datum_is_default(&new[index], &c->column->type)) 
>>> {
>>> +                    continue;
>>>                   }
>>> +
>>> +                column_update = &new[index];
>>> +            }
>>> +
>>> +            if (!empty) {
>>> +                ds_put_char(ds, ',');
>>> +            }
>>> +            empty = false;
>>> +            json_string_escape(c->column->name, ds);
>>> +            ds_put_char(ds, ':');
>>> +            ovsdb_datum_to_json_ds(column_update, &c->column->type, ds);
>>> +
>>> +            if (type == OJMS_MODIFY) {
>>> +                ovsdb_datum_destroy(&diff, &c->column->type);
>>>               }
>>>           }
>>>
>>> -        op = type == OJMS_INITIAL ? "initial"
>>> -                                  : type == OJMS_MODIFY ? "modify" : 
>>> "insert";
>>> -        json_object_put(row_update2, op, diff_json);
>>> +        ds_put_char(ds, '}');
>>>       }
>>> +    ds_put_char(ds, '}');
>>>
>>> -    return row_update2;
>>> +    return true;
>>>   }
>>>
>>>   static size_t
>>> @@ -1166,6 +1210,8 @@ ovsdb_monitor_compose_update(
>>
>> Same comments here, as for ovsdb_monitor_compose_cond_change_update() below 
>> (sorry I started with the wrong function).
>>
>
> No problem, see corresponding comments below.
>
>>>       size_t max_columns = ovsdb_monitor_max_columns(dbmon);
>>>       unsigned long int *changed = xmalloc(bitmap_n_bytes(max_columns));
>>>
>>> +    struct ds ds;
>>> +    ds_init(&ds);
>>>       json = NULL;
>>>       struct ovsdb_monitor_change_set_for_table *mcst;
>>>       LIST_FOR_EACH (mcst, list_in_change_set, &mcs->change_set_for_tables) 
>>> {
>>> @@ -1176,16 +1222,24 @@ ovsdb_monitor_compose_update(
>>>           HMAP_FOR_EACH_SAFE (row, hmap_node, &mcst->rows) {
>>>               cooperative_multitasking_yield();
>>>
>>> -            struct json *row_json;
>>> -            row_json = (*row_update)(mt, condition, OVSDB_MONITOR_ROW, row,
>>> -                                     initial, changed, mcst->n_columns);
>>> -            if (row_json) {
>>> +            bool have_update;
>>> +            have_update = (*row_update)(mt, condition, OVSDB_MONITOR_ROW, 
>>> row,
>>> +                                        initial, changed, mcst->n_columns,
>>> +                                        &ds);
>>> +            if (have_update) {
>>> +                struct json *row_json;
>>> +                /* row_json content is only used for serialization */
>>> +                row_json =
>>> +                    
>>> json_serialized_object_create_from_string(ds_cstr_ro(&ds));
>>> +                ds_clear(&ds);
>>> +
>>>                   ovsdb_monitor_add_json_row(&json, mt->table->schema->name,
>>>                                              &table_json, row_json,
>>>                                              &row->uuid);
>>>               }
>>>           }
>>>       }
>>> +    ds_destroy(&ds);
>>>       free(changed);
>>>
>>>       return json;
>>> @@ -1201,6 +1255,9 @@ ovsdb_monitor_compose_cond_change_update(
>>>       size_t max_columns = ovsdb_monitor_max_columns(dbmon);
>>>       unsigned long int *changed = xmalloc(bitmap_n_bytes(max_columns));
>>>
>>> +    struct ds ds;
>>> +    ds_init(&ds);
>>
>> Maybe do ‘struct ds ds = DS_EMPTY_INITIALIZER;’ and move it higher at the 
>> right place (reverse Christmas tree order) with the other definitions.
>
> Agreed.
>
>>
>>> +
>>>       SHASH_FOR_EACH (node, &dbmon->tables) {
>>>           struct ovsdb_condition *old_condition, *new_condition, 
>>> *diff_condition;
>>>           struct ovsdb_monitor_table *mt = node->data;
>>> @@ -1219,15 +1276,21 @@ ovsdb_monitor_compose_cond_change_update(
>>>
>>>           /* Iterate over all rows in table */
>>>           HMAP_FOR_EACH (row, hmap_node, &mt->table->rows) {
>>> -            struct json *row_json;
>>> -
>>>               cooperative_multitasking_yield();
>>>
>>> -            row_json = ovsdb_monitor_compose_row_update2(mt, condition,
>>> -                                                         OVSDB_ROW, row,
>>> -                                                         false, changed,
>>> -                                                         mt->n_columns);
>>> -            if (row_json) {
>>> +            bool have_update;
>>> +            have_update = ovsdb_monitor_compose_row_update2(mt, condition,
>>> +                                                            OVSDB_ROW, row,
>>> +                                                            false, changed,
>>> +                                                            mt->n_columns,
>>> +                                                            &ds);
>>> +            if (have_update) {
>>> +                struct json *row_json;
>>> +                /* row_json content is only used for serialization */
>>> +                row_json =
>>> +                    
>>> json_serialized_object_create_from_string(ds_cstr_ro(&ds));
>>
>> Maybe create a function in the likes of 
>> json_serialized_object_create_from_string_nocopy() (see 
>> json_string_create_nocopy()), and use ds_steal_cstr() so we do not need to 
>> allocate and copy the memory. This should improve your results even more ;)
>>
>
> This way we will lose more. If we steal buffer from ds, on the next iteration 
> it will have to be grown again. Which is more expensive than plain copying. 
> Moreover, in case of copy we will allocate strictly the needed space. In case 
> of stealing we will have excessive tails in every stolen string. This affects 
> peak memory consumption.
>
> Possible optimization could be to steal in the end, on the last iteration. 
> But at this moment we won't probably have an update, or there will be too 
> much of copyings anyway. It's still possible to defer copying till the next 
> iteration, so that we can do stealing after all iterations, but I don't 
> believe the result is worth the effort.
>
> Meanwhile, one thing could be done. We can accept in 
> json_serialized_object_create_from_string both source buffer and length, and 
> thus avoid calling strlen, which will improve time. Or we can accept ds 
> itself. I think I will do it, though I don't think this is the slowest place 
> anymore.

Maybe I need more coffee, but this should not matter in this case as you will 
start with a new ds() each round. Let me add some code here, maybe it becomes 
more clear what I meant (not tested):

diff --git a/lib/json.c b/lib/json.c
index 66b1f571f..099d6f3fd 100644
--- a/lib/json.c
+++ b/lib/json.c
@@ -206,6 +206,13 @@ json_serialized_object_create_from_string(const char *s)
     json->string = xstrdup(s);
     return json;
 }
+struct json *
+json_serialized_object_create_from_string_nocopy(const char *s)
+{
+    struct json *json = json_create(JSON_SERIALIZED_OBJECT);
+    json->string = s;
+    return json;
+}

 struct json *
 json_serialized_object_create_with_yield(const struct json *src)
diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
index 8355b8da7..3e6b3ddd1 100644
--- a/ovsdb/monitor.c
+++ b/ovsdb/monitor.c
@@ -1230,8 +1230,8 @@ ovsdb_monitor_compose_update(
                 struct json *row_json;
                 /* row_json content is only used for serialization */
                 row_json =
-                    json_serialized_object_create_from_string(ds_cstr_ro(&ds));
-                ds_clear(&ds);
+                    json_serialized_object_create_from_string_nocopy(
+                        ds_steal_cstr(ds));

                 ovsdb_monitor_add_json_row(&json, mt->table->schema->name,
                                            &table_json, row_json,

Now each round you will start with a new (clean) ds, as before, but you do not 
have the xstrdup(s). Am I missing something?

>>> +                ds_clear(&ds);
>>
>> Not needed if you use ds_steal_cstr()
>
> See above.
>
>>
>>> +
>>>                   ovsdb_monitor_add_json_row(&json, mt->table->schema->name,
>>>                                              &table_json, row_json,
>>>                                              ovsdb_row_get_uuid(row));
>>> @@ -1235,6 +1298,7 @@ ovsdb_monitor_compose_cond_change_update(
>>>           }
>>>           ovsdb_monitor_table_condition_updated(mt, condition);
>>>       }
>>> +    ds_destroy(&ds);
>>>       free(changed);
>>>
>>>       return json;
>>> -- 
>>> 2.45.2
>>>
>>> _______________________________________________
>>> dev mailing list
>>> d...@openvswitch.org
>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>
> -- 
> Best,
> Grigorii

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to