mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 09:58:01 +00:00
ovsdb-data: Add function to apply diff in-place.
ovsdb_datum_apply_diff() is heavily used in ovsdb transactions, but it's linear in terms of number of comparisons. And it also clones all the atoms along the way. In most cases size of a diff is much smaller than the size of the original datum, this allows to perform the same operation in-place with only O(diff->n * log2(old->n)) comparisons and O(old->n + diff->n) memory copies with memcpy. Using this function while applying diffs read from the storage gives a significant performance boost and allows to execute much more transactions per second. Signed-off-by: Ilya Maximets <i.maximets@ovn.org> Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
This commit is contained in:
parent
bb12b63176
commit
32b51326ef
100
lib/ovsdb-data.c
100
lib/ovsdb-data.c
@ -2253,6 +2253,106 @@ ovsdb_datum_diff(struct ovsdb_datum *diff,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply 'diff' to 'a'.
|
||||||
|
*
|
||||||
|
* Return NULL if the 'a' is successfully updated, otherwise, return
|
||||||
|
* ovsdb_error. */
|
||||||
|
struct ovsdb_error *
|
||||||
|
ovsdb_datum_apply_diff_in_place(struct ovsdb_datum *a,
|
||||||
|
const struct ovsdb_datum *diff,
|
||||||
|
const struct ovsdb_type *type)
|
||||||
|
{
|
||||||
|
struct ovsdb_error *error = NULL;
|
||||||
|
struct ovsdb_datum result;
|
||||||
|
size_t i, new_size;
|
||||||
|
unsigned int *idx, pos;
|
||||||
|
enum {
|
||||||
|
DIFF_OP_ADD,
|
||||||
|
DIFF_OP_REMOVE,
|
||||||
|
DIFF_OP_UPDATE,
|
||||||
|
} *operation;
|
||||||
|
|
||||||
|
if (!ovsdb_type_is_composite(type)) {
|
||||||
|
ovsdb_datum_destroy(a, type);
|
||||||
|
ovsdb_datum_clone(a, diff, type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
operation = xmalloc(diff->n * sizeof *operation);
|
||||||
|
idx = xmalloc(diff->n * sizeof *idx);
|
||||||
|
new_size = a->n;
|
||||||
|
for (i = 0; i < diff->n; i++) {
|
||||||
|
if (!ovsdb_datum_find_key(a, &diff->keys[i], type->key.type, &pos)) {
|
||||||
|
operation[i] = DIFF_OP_ADD;
|
||||||
|
new_size++;
|
||||||
|
} else if (type->value.type != OVSDB_TYPE_VOID
|
||||||
|
&& !ovsdb_atom_equals(&diff->values[i], &a->values[pos],
|
||||||
|
type->value.type)) {
|
||||||
|
operation[i] = DIFF_OP_UPDATE;
|
||||||
|
} else {
|
||||||
|
operation[i] = DIFF_OP_REMOVE;
|
||||||
|
new_size--;
|
||||||
|
}
|
||||||
|
idx[i] = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure member size of 'new' conforms to type. */
|
||||||
|
if (new_size < type->n_min || new_size > type->n_max) {
|
||||||
|
error = ovsdb_error(NULL, "Datum crated by diff has size error");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ovsdb_datum_init_empty(&result);
|
||||||
|
ovsdb_datum_reallocate(&result, type, new_size);
|
||||||
|
|
||||||
|
unsigned int copied = 0;
|
||||||
|
for (i = 0; i < diff->n; i++) {
|
||||||
|
pos = idx[i];
|
||||||
|
|
||||||
|
if (copied < pos) {
|
||||||
|
/* Copying all atoms that should go before the current one. */
|
||||||
|
ovsdb_datum_push_unsafe(&result, a, copied, pos - copied, type);
|
||||||
|
copied = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (operation[i]) {
|
||||||
|
case DIFF_OP_UPDATE:
|
||||||
|
case DIFF_OP_ADD:
|
||||||
|
/* Inserting new atom from 'diff'. */
|
||||||
|
ovsdb_atom_clone(&result.keys[result.n],
|
||||||
|
&diff->keys[i], type->key.type);
|
||||||
|
if (type->value.type != OVSDB_TYPE_VOID) {
|
||||||
|
ovsdb_atom_clone(&result.values[result.n],
|
||||||
|
&diff->values[i], type->value.type);
|
||||||
|
}
|
||||||
|
result.n++;
|
||||||
|
if (operation[i] != DIFF_OP_UPDATE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* fall through */
|
||||||
|
|
||||||
|
case DIFF_OP_REMOVE:
|
||||||
|
/* Destroying atom. */
|
||||||
|
ovsdb_atom_destroy(&a->keys[pos], type->key.type);
|
||||||
|
if (type->value.type != OVSDB_TYPE_VOID) {
|
||||||
|
ovsdb_atom_destroy(&a->values[pos], type->value.type);
|
||||||
|
}
|
||||||
|
copied++; /* Skipping removed atom. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Copying remaining atoms. */
|
||||||
|
ovsdb_datum_push_unsafe(&result, a, copied, a->n - copied, type);
|
||||||
|
a->n = 0;
|
||||||
|
|
||||||
|
ovsdb_datum_swap(&result, a);
|
||||||
|
ovsdb_datum_destroy(&result, type);
|
||||||
|
exit:
|
||||||
|
free(operation);
|
||||||
|
free(idx);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/* Apply 'diff' to 'old' to regenerate 'new'.
|
/* Apply 'diff' to 'old' to regenerate 'new'.
|
||||||
*
|
*
|
||||||
* Return NULL if the 'new' is successfully generated, otherwise, return
|
* Return NULL if the 'new' is successfully generated, otherwise, return
|
||||||
|
@ -252,6 +252,12 @@ struct ovsdb_error *ovsdb_datum_apply_diff(struct ovsdb_datum *new_datum,
|
|||||||
const struct ovsdb_type *type)
|
const struct ovsdb_type *type)
|
||||||
OVS_WARN_UNUSED_RESULT;
|
OVS_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
struct ovsdb_error * ovsdb_datum_apply_diff_in_place(
|
||||||
|
struct ovsdb_datum *a,
|
||||||
|
const struct ovsdb_datum *diff,
|
||||||
|
const struct ovsdb_type *type)
|
||||||
|
OVS_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
/* Raw operations that may not maintain the invariants. */
|
/* Raw operations that may not maintain the invariants. */
|
||||||
void ovsdb_datum_remove_unsafe(struct ovsdb_datum *, size_t idx,
|
void ovsdb_datum_remove_unsafe(struct ovsdb_datum *, size_t idx,
|
||||||
const struct ovsdb_type *);
|
const struct ovsdb_type *);
|
||||||
|
10
ovsdb/file.c
10
ovsdb/file.c
@ -113,19 +113,17 @@ ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
|
|||||||
if (row_contains_diff
|
if (row_contains_diff
|
||||||
&& !ovsdb_datum_is_default(&row->fields[column->index],
|
&& !ovsdb_datum_is_default(&row->fields[column->index],
|
||||||
&column->type)) {
|
&column->type)) {
|
||||||
struct ovsdb_datum new_datum;
|
error = ovsdb_datum_apply_diff_in_place(
|
||||||
|
|
||||||
error = ovsdb_datum_apply_diff(&new_datum,
|
|
||||||
&row->fields[column->index],
|
&row->fields[column->index],
|
||||||
&datum, &column->type);
|
&datum, &column->type);
|
||||||
ovsdb_datum_destroy(&datum, &column->type);
|
ovsdb_datum_destroy(&datum, &column->type);
|
||||||
if (error) {
|
if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
ovsdb_datum_swap(&datum, &new_datum);
|
} else {
|
||||||
|
ovsdb_datum_swap(&row->fields[column->index], &datum);
|
||||||
|
ovsdb_datum_destroy(&datum, &column->type);
|
||||||
}
|
}
|
||||||
ovsdb_datum_swap(&row->fields[column->index], &datum);
|
|
||||||
ovsdb_datum_destroy(&datum, &column->type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -846,18 +846,21 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- integer],
|
|||||||
[[diff-data '["integer"]' '[0]' '[2]']],
|
[[diff-data '["integer"]' '[0]' '[2]']],
|
||||||
[[diff: 2
|
[[diff: 2
|
||||||
apply diff: 2
|
apply diff: 2
|
||||||
|
apply diff in place: 2
|
||||||
OK]])
|
OK]])
|
||||||
|
|
||||||
OVSDB_CHECK_POSITIVE([generate and apply diff -- boolean],
|
OVSDB_CHECK_POSITIVE([generate and apply diff -- boolean],
|
||||||
[[diff-data '["boolean"]' '[true]' '[false]']],
|
[[diff-data '["boolean"]' '[true]' '[false]']],
|
||||||
[[diff: false
|
[[diff: false
|
||||||
apply diff: false
|
apply diff: false
|
||||||
|
apply diff in place: false
|
||||||
OK]])
|
OK]])
|
||||||
|
|
||||||
OVSDB_CHECK_POSITIVE([generate and apply diff -- string],
|
OVSDB_CHECK_POSITIVE([generate and apply diff -- string],
|
||||||
[[diff-data '["string"]' '["AAA"]' '["BBB"]']],
|
[[diff-data '["string"]' '["AAA"]' '["BBB"]']],
|
||||||
[[diff: "BBB"
|
[[diff: "BBB"
|
||||||
apply diff: "BBB"
|
apply diff: "BBB"
|
||||||
|
apply diff in place: "BBB"
|
||||||
OK]])
|
OK]])
|
||||||
|
|
||||||
dnl Test set modifications.
|
dnl Test set modifications.
|
||||||
@ -870,15 +873,19 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- set],
|
|||||||
]],
|
]],
|
||||||
[[diff: ["set",[0,2]]
|
[[diff: ["set",[0,2]]
|
||||||
apply diff: ["set",[1,2]]
|
apply diff: ["set",[1,2]]
|
||||||
|
apply diff in place: ["set",[1,2]]
|
||||||
OK
|
OK
|
||||||
diff: 0
|
diff: 0
|
||||||
apply diff: 1
|
apply diff: 1
|
||||||
|
apply diff in place: 1
|
||||||
OK
|
OK
|
||||||
diff: ["set",[0,1]]
|
diff: ["set",[0,1]]
|
||||||
apply diff: ["set",[0,1]]
|
apply diff: ["set",[0,1]]
|
||||||
|
apply diff in place: ["set",[0,1]]
|
||||||
OK
|
OK
|
||||||
diff: ["set",[0,1]]
|
diff: ["set",[0,1]]
|
||||||
apply diff: ["set",[]]
|
apply diff: ["set",[]]
|
||||||
|
apply diff in place: ["set",[]]
|
||||||
OK]])
|
OK]])
|
||||||
|
|
||||||
dnl Test set modifications causes data to violate set size constrain.
|
dnl Test set modifications causes data to violate set size constrain.
|
||||||
@ -898,18 +905,23 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- map],
|
|||||||
]],
|
]],
|
||||||
[[diff: ["map",[["2 gills","1 chopin"],["2 pints","1 quart"]]]
|
[[diff: ["map",[["2 gills","1 chopin"],["2 pints","1 quart"]]]
|
||||||
apply diff: ["map",[["2 pints","1 quart"]]]
|
apply diff: ["map",[["2 pints","1 quart"]]]
|
||||||
|
apply diff in place: ["map",[["2 pints","1 quart"]]]
|
||||||
OK
|
OK
|
||||||
diff: ["map",[]]
|
diff: ["map",[]]
|
||||||
apply diff: ["map",[["2 gills","1 chopin"]]]
|
apply diff: ["map",[["2 gills","1 chopin"]]]
|
||||||
|
apply diff in place: ["map",[["2 gills","1 chopin"]]]
|
||||||
OK
|
OK
|
||||||
diff: ["map",[["2 gills","1 chopin"]]]
|
diff: ["map",[["2 gills","1 chopin"]]]
|
||||||
apply diff: ["map",[]]
|
apply diff: ["map",[]]
|
||||||
|
apply diff in place: ["map",[]]
|
||||||
OK
|
OK
|
||||||
diff: ["map",[["2 pints","1 quart"]]]
|
diff: ["map",[["2 pints","1 quart"]]]
|
||||||
apply diff: ["map",[["2 pints","1 quart"]]]
|
apply diff: ["map",[["2 pints","1 quart"]]]
|
||||||
|
apply diff in place: ["map",[["2 pints","1 quart"]]]
|
||||||
OK
|
OK
|
||||||
diff: ["map",[["2 gills","1 gallon"]]]
|
diff: ["map",[["2 gills","1 gallon"]]]
|
||||||
apply diff: ["map",[["2 gills","1 gallon"]]]
|
apply diff: ["map",[["2 gills","1 gallon"]]]
|
||||||
|
apply diff in place: ["map",[["2 gills","1 gallon"]]]
|
||||||
OK]])
|
OK]])
|
||||||
|
|
||||||
OVSDB_CHECK_NEGATIVE([generate and apply diff with map -- size error],
|
OVSDB_CHECK_NEGATIVE([generate and apply diff with map -- size error],
|
||||||
|
@ -512,6 +512,18 @@ do_diff_data(struct ovs_cmdl_context *ctx)
|
|||||||
ovs_fatal(0, "failed to apply diff");
|
ovs_fatal(0, "failed to apply diff");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply diff to 'old' in place. */
|
||||||
|
error = ovsdb_datum_apply_diff_in_place(&old, &diff, &type);
|
||||||
|
if (error) {
|
||||||
|
char *string = ovsdb_error_to_string_free(error);
|
||||||
|
ovs_fatal(0, "%s", string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test to make sure 'old' equals 'new' now. */
|
||||||
|
if (!ovsdb_datum_equals(&new, &old, &type)) {
|
||||||
|
ovs_fatal(0, "failed to apply diff in place");
|
||||||
|
}
|
||||||
|
|
||||||
/* Print diff */
|
/* Print diff */
|
||||||
json = ovsdb_datum_to_json(&diff, &type);
|
json = ovsdb_datum_to_json(&diff, &type);
|
||||||
printf ("diff: ");
|
printf ("diff: ");
|
||||||
@ -522,6 +534,11 @@ do_diff_data(struct ovs_cmdl_context *ctx)
|
|||||||
printf ("apply diff: ");
|
printf ("apply diff: ");
|
||||||
print_and_free_json(json);
|
print_and_free_json(json);
|
||||||
|
|
||||||
|
/* Print updated 'old' */
|
||||||
|
json = ovsdb_datum_to_json(&old, &type);
|
||||||
|
printf ("apply diff in place: ");
|
||||||
|
print_and_free_json(json);
|
||||||
|
|
||||||
ovsdb_datum_destroy(&new, &type);
|
ovsdb_datum_destroy(&new, &type);
|
||||||
ovsdb_datum_destroy(&old, &type);
|
ovsdb_datum_destroy(&old, &type);
|
||||||
ovsdb_datum_destroy(&diff, &type);
|
ovsdb_datum_destroy(&diff, &type);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user