2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-24 10:58:04 +00:00

193 lines
6.3 KiB
C
Raw Normal View History

/*
* Copyright (c) 2009, 2010, 2015, 2016 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OPENVSWITCH_JSON_H
#define OPENVSWITCH_JSON_H 1
/* This is an implementation of JavaScript Object Notation (JSON) as specified
* by RFC 4627. It is intended to fully comply with RFC 4627, with the
* following known exceptions and clarifications:
*
* - Null bytes (\u0000) are not allowed in strings.
*
* - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
* encodings).
*
* - Names within an object must be unique (RFC 4627 says that they
* "should" be unique).
*/
#include <stdio.h>
#include "openvswitch/shash.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ds;
struct uuid;
/* Type of a JSON value. */
enum json_type {
JSON_NULL, /* null */
JSON_FALSE, /* false */
JSON_TRUE, /* true */
JSON_OBJECT, /* {"a": b, "c": d, ...} */
JSON_ARRAY, /* [1, 2, 3, ...] */
JSON_INTEGER, /* 123. */
JSON_REAL, /* 123.456. */
JSON_STRING, /* "..." */
JSON_N_TYPES,
JSON_SERIALIZED_OBJECT, /* Internal type to hold serialized version of
* data of other types. */
};
const char *json_type_to_string(enum json_type);
/* A JSON array. */
struct json_array {
size_t n, n_allocated;
struct json **elems;
};
json: Store short strings in-place. The 'struct json' contains a union and the largest element of that union is 'struct json_array', which takes 24 bytes. It means, that a lot of space in this structure remains unused whenever the type is not JSON_ARRAY. For example, the 'string' pointer used for JSON_STRING only takes 8 bytes on a 64-bit system leaving 24 - 8 = 16 bytes unused. There is also a 4-byte hole between the 'type' and the 'count'. A pretty common optimization technique for storing strings is to store short ones in place of the pointer and only allocate dynamically the larger strings that do not fit. In our case, we have even larger space of 24 bytes to work with. So, we could use all 24 bytes to store the strings (23 string bytes + '\0') and use the 4 byte unused space outside the union to store the storage type. This approach should allow us to save on memory allocation for short strings and also save on accesses to them, as the content will fit into the same cache line as the 'struct json' itself. In practice, large OVN databases tend to operate with quite large strings. For example, all the logical flow matches and actions in OVN Southbound database would not fit. However, this approach still allows to improve performance with large OVN databases. With 350MB OVN Northbound database with 12M atoms: Before After Improvement ovsdb-client dump 18.6 sec 16.6 sec 10.7 % Compaction 14.0 sec 13.4 sec 4.2 % Memory usage (RSS) 2.28 GB 2.05 GB 10.0 % With 615MB OVN Southbound database with 23M atoms: Before After Improvement ovsdb-client dump 46.1 sec 43.7 sec 5.2 % Compaction 34.8 sec 32.5 sec 6.6 % Memory usage (RSS) 5.29 GB 4.80 GB 9.3 % In the results above, 'ovsdb-client dump' is measuring how log it takes for the server to prepare and send a reply, 'Memory usage (RSS)' reflects the RSS of the ovsdb-server after loading the full database. ovn-heater tests report similar reduction in CPU and memory usage on heavy operations like compaction. Acked-by: Mike Pattrick <mkp@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-06-24 21:54:32 +02:00
/* Maximum string length that can be stored inline ('\0' is not included). */
#define JSON_STRING_INLINE_LEN (sizeof(struct json_array) - 1)
enum json_storage_type {
JSON_STRING_DYNAMIC = 0, /* JSON_STRING is stored via 'str_ptr'. */
JSON_STRING_INLINE, /* JSON_STRING is stored in 'str' array. */
};
/* A JSON value. */
struct json {
enum json_type type;
json: Store short strings in-place. The 'struct json' contains a union and the largest element of that union is 'struct json_array', which takes 24 bytes. It means, that a lot of space in this structure remains unused whenever the type is not JSON_ARRAY. For example, the 'string' pointer used for JSON_STRING only takes 8 bytes on a 64-bit system leaving 24 - 8 = 16 bytes unused. There is also a 4-byte hole between the 'type' and the 'count'. A pretty common optimization technique for storing strings is to store short ones in place of the pointer and only allocate dynamically the larger strings that do not fit. In our case, we have even larger space of 24 bytes to work with. So, we could use all 24 bytes to store the strings (23 string bytes + '\0') and use the 4 byte unused space outside the union to store the storage type. This approach should allow us to save on memory allocation for short strings and also save on accesses to them, as the content will fit into the same cache line as the 'struct json' itself. In practice, large OVN databases tend to operate with quite large strings. For example, all the logical flow matches and actions in OVN Southbound database would not fit. However, this approach still allows to improve performance with large OVN databases. With 350MB OVN Northbound database with 12M atoms: Before After Improvement ovsdb-client dump 18.6 sec 16.6 sec 10.7 % Compaction 14.0 sec 13.4 sec 4.2 % Memory usage (RSS) 2.28 GB 2.05 GB 10.0 % With 615MB OVN Southbound database with 23M atoms: Before After Improvement ovsdb-client dump 46.1 sec 43.7 sec 5.2 % Compaction 34.8 sec 32.5 sec 6.6 % Memory usage (RSS) 5.29 GB 4.80 GB 9.3 % In the results above, 'ovsdb-client dump' is measuring how log it takes for the server to prepare and send a reply, 'Memory usage (RSS)' reflects the RSS of the ovsdb-server after loading the full database. ovn-heater tests report similar reduction in CPU and memory usage on heavy operations like compaction. Acked-by: Mike Pattrick <mkp@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-06-24 21:54:32 +02:00
enum json_storage_type storage_type;
json: Use reference counting in JSON objects After profiling OVSDB insert performance it was found that some significant portion of its time OVSDB is calling the function json_clone. Also, the current usages of json_clone never modify the json, just keeps it to prevent it to be freed. With that in mind the struct json, json_create, json_clone and json_destroy were modified to keep a count of how many references of the json struct are left. Only when that count reaches zero the json struct is freed. The old "json_clone" function was renamed as "json_deep_clone". Some examples of the performance difference: In these tests a test table with 4 columns (string, string, bool, integer) was used. All the tests used "commit block". *** 50 process each inserting 1000 rows *** Master OVS Test Duration 131 seconds Average Inserts Per second 746.2687 inserts/s Average Insert Duration 134.1382 ms Minimal Insert Duration 0.166202 ms Maximum Insert Duration 489.8593 ms JSON GC Patch Test Duration 86 seconds Average Inserts Per second 1176 inserts/s Average Insert Duration 82.26761 ms Minimal Insert Duration 0.165448 ms Maximum Insert Duration 751.2111 ms *** 5 process each inserting 10000 rows *** Master OVS Test Duration 8 seconds Average Inserts Per second 7142.857 inserts/s Average Insert Duration 0.656431 ms Minimal Insert Duration 0.125197 ms Maximum Insert Duration 11.93203 ms JSON GC Patch Test Duration 7 seconds Average Inserts Per second 8333.333 inserts/s Average Insert Duration 0.55688 ms Minimal Insert Duration 0.143233 ms Maximum Insert Duration 26.26319 ms Signed-off-by: Esteban Rodriguez Betancourt <estebarb@hpe.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
2016-10-04 19:31:48 +00:00
size_t count;
union {
struct shash *object; /* Contains "struct json *"s. */
struct json_array array;
long long int integer;
double real;
json: Store short strings in-place. The 'struct json' contains a union and the largest element of that union is 'struct json_array', which takes 24 bytes. It means, that a lot of space in this structure remains unused whenever the type is not JSON_ARRAY. For example, the 'string' pointer used for JSON_STRING only takes 8 bytes on a 64-bit system leaving 24 - 8 = 16 bytes unused. There is also a 4-byte hole between the 'type' and the 'count'. A pretty common optimization technique for storing strings is to store short ones in place of the pointer and only allocate dynamically the larger strings that do not fit. In our case, we have even larger space of 24 bytes to work with. So, we could use all 24 bytes to store the strings (23 string bytes + '\0') and use the 4 byte unused space outside the union to store the storage type. This approach should allow us to save on memory allocation for short strings and also save on accesses to them, as the content will fit into the same cache line as the 'struct json' itself. In practice, large OVN databases tend to operate with quite large strings. For example, all the logical flow matches and actions in OVN Southbound database would not fit. However, this approach still allows to improve performance with large OVN databases. With 350MB OVN Northbound database with 12M atoms: Before After Improvement ovsdb-client dump 18.6 sec 16.6 sec 10.7 % Compaction 14.0 sec 13.4 sec 4.2 % Memory usage (RSS) 2.28 GB 2.05 GB 10.0 % With 615MB OVN Southbound database with 23M atoms: Before After Improvement ovsdb-client dump 46.1 sec 43.7 sec 5.2 % Compaction 34.8 sec 32.5 sec 6.6 % Memory usage (RSS) 5.29 GB 4.80 GB 9.3 % In the results above, 'ovsdb-client dump' is measuring how log it takes for the server to prepare and send a reply, 'Memory usage (RSS)' reflects the RSS of the ovsdb-server after loading the full database. ovn-heater tests report similar reduction in CPU and memory usage on heavy operations like compaction. Acked-by: Mike Pattrick <mkp@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-06-24 21:54:32 +02:00
union {
char str[JSON_STRING_INLINE_LEN + 1];
char *str_ptr;
}; /* JSON_STRING or JSON_SERIALIZED_OBJECT. */
};
};
struct json *json_null_create(void);
struct json *json_boolean_create(bool);
struct json *json_string_create(const char *);
struct json *json_string_create_nocopy(char *);
struct json *json_string_create_uuid(const struct uuid *);
struct json *json_serialized_object_create(const struct json *);
struct json *json_integer_create(long long int);
struct json *json_real_create(double);
struct json *json_array_create_empty(void);
void json_array_add(struct json *, struct json *element);
void json_array_trim(struct json *);
struct json *json_array_create(struct json **, size_t n);
struct json *json_array_create_1(struct json *);
struct json *json_array_create_2(struct json *, struct json *);
struct json *json_array_create_3(struct json *, struct json *, struct json *);
bool json_array_contains_string(const struct json *, const char *);
struct json *json_object_create(void);
void json_object_put(struct json *, const char *name, struct json *value);
void json_object_put_nocopy(struct json *, char *name, struct json *value);
void json_object_put_string(struct json *,
const char *name, const char *value);
void json_object_put_format(struct json *,
const char *name, const char *format, ...)
OVS_PRINTF_FORMAT(3, 4);
void json_object_put_uuid(struct json *, const char *name,
const struct uuid *);
const char *json_string(const struct json *);
const char *json_serialized_object(const struct json *);
struct json_array *json_array(const struct json *);
struct shash *json_object(const struct json *);
bool json_boolean(const struct json *);
double json_real(const struct json *);
int64_t json_integer(const struct json *);
json: Use reference counting in JSON objects After profiling OVSDB insert performance it was found that some significant portion of its time OVSDB is calling the function json_clone. Also, the current usages of json_clone never modify the json, just keeps it to prevent it to be freed. With that in mind the struct json, json_create, json_clone and json_destroy were modified to keep a count of how many references of the json struct are left. Only when that count reaches zero the json struct is freed. The old "json_clone" function was renamed as "json_deep_clone". Some examples of the performance difference: In these tests a test table with 4 columns (string, string, bool, integer) was used. All the tests used "commit block". *** 50 process each inserting 1000 rows *** Master OVS Test Duration 131 seconds Average Inserts Per second 746.2687 inserts/s Average Insert Duration 134.1382 ms Minimal Insert Duration 0.166202 ms Maximum Insert Duration 489.8593 ms JSON GC Patch Test Duration 86 seconds Average Inserts Per second 1176 inserts/s Average Insert Duration 82.26761 ms Minimal Insert Duration 0.165448 ms Maximum Insert Duration 751.2111 ms *** 5 process each inserting 10000 rows *** Master OVS Test Duration 8 seconds Average Inserts Per second 7142.857 inserts/s Average Insert Duration 0.656431 ms Minimal Insert Duration 0.125197 ms Maximum Insert Duration 11.93203 ms JSON GC Patch Test Duration 7 seconds Average Inserts Per second 8333.333 inserts/s Average Insert Duration 0.55688 ms Minimal Insert Duration 0.143233 ms Maximum Insert Duration 26.26319 ms Signed-off-by: Esteban Rodriguez Betancourt <estebarb@hpe.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
2016-10-04 19:31:48 +00:00
struct json *json_deep_clone(const struct json *);
static inline struct json *json_clone(const struct json *);
struct json *json_nullable_clone(const struct json *);
static inline void json_destroy(struct json *);
size_t json_hash(const struct json *, size_t basis);
bool json_equal(const struct json *, const struct json *);
/* Parsing JSON. */
enum {
JSPF_TRAILER = 1 << 0 /* Check for garbage following input. */
};
struct json_parser *json_parser_create(int flags);
size_t json_parser_feed(struct json_parser *, const char *, size_t);
bool json_parser_is_done(const struct json_parser *);
struct json *json_parser_finish(struct json_parser *);
void json_parser_abort(struct json_parser *);
struct json *json_from_string(const char *string);
struct json *json_from_serialized_object(const struct json *);
struct json *json_from_file(const char *file_name);
struct json *json_from_stream(FILE *stream);
/* Serializing JSON. */
enum {
JSSF_PRETTY = 1 << 0, /* Multiple lines with indentation, if true. */
JSSF_SORT = 1 << 1 /* Object members in sorted order, if true. */
};
char *json_to_string(const struct json *, int flags);
void json_to_ds(const struct json *, int flags, struct ds *);
/* JSON string formatting operations. */
bool json_string_unescape(const char *in, size_t in_len, char **outp);
void json_string_escape(const char *in, struct ds *out);
/* Inline functions. */
/* Returns 'json', with the reference count incremented. */
static inline struct json *
json_clone(const struct json *json_)
{
struct json *json = CONST_CAST(struct json *, json_);
json->count++;
return json;
}
void json_destroy__(struct json *json, bool);
/* Frees 'json' and everything it points to, recursively. */
static inline void
json_destroy(struct json *json)
{
if (json && !--json->count) {
json_destroy__(json, false);
}
}
#ifdef __cplusplus
}
#endif
#endif /* OPENVSWITCH_JSON_H */