mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 09:58:01 +00:00
Re-write cmap's loops using multi-variable helpers. For iterators based on cmap_cursor, we just need to make sure the NODE variable is not used after the loop, so we set it to NULL. Acked-by: Eelco Chaudron <echaudro@redhat.com> Acked-by: Dumitru Ceara <dceara@redhat.com> Signed-off-by: Adrian Moreno <amorenoz@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
656 lines
16 KiB
C
656 lines
16 KiB
C
/*
|
||
* Copyright (c) 2008, 2009, 2010, 2013, 2014, 2016, 2017 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.
|
||
*/
|
||
|
||
/* A non-exhaustive test for some of the functions and macros declared in
|
||
* cmap.h. */
|
||
|
||
#include <config.h>
|
||
#undef NDEBUG
|
||
#include "cmap.h"
|
||
#include <assert.h>
|
||
#include <getopt.h>
|
||
#include <string.h>
|
||
#include "bitmap.h"
|
||
#include "command-line.h"
|
||
#include "fat-rwlock.h"
|
||
#include "hash.h"
|
||
#include "openvswitch/hmap.h"
|
||
#include "ovstest.h"
|
||
#include "ovs-thread.h"
|
||
#include "random.h"
|
||
#include "timeval.h"
|
||
#include "util.h"
|
||
|
||
/* Sample cmap element. */
|
||
struct element {
|
||
int value;
|
||
struct cmap_node node;
|
||
};
|
||
|
||
typedef size_t hash_func(int value);
|
||
|
||
static int
|
||
compare_ints(const void *a_, const void *b_)
|
||
{
|
||
const int *a = a_;
|
||
const int *b = b_;
|
||
return *a < *b ? -1 : *a > *b;
|
||
}
|
||
|
||
/* Verifies that 'cmap' contains exactly the 'n' values in 'values'. */
|
||
static void
|
||
check_cmap(struct cmap *cmap, const int values[], size_t n,
|
||
hash_func *hash)
|
||
{
|
||
int *sort_values, *cmap_values, *cmap_values2;
|
||
const struct element *e;
|
||
size_t i, batch_size;
|
||
|
||
struct cmap_position pos = { 0, 0, 0 };
|
||
struct cmap_node *node;
|
||
|
||
/* Check that all the values are there in iteration. */
|
||
sort_values = xmalloc(sizeof *sort_values * n);
|
||
cmap_values = xmalloc(sizeof *sort_values * n);
|
||
cmap_values2 = xmalloc(sizeof *sort_values * n);
|
||
|
||
/* Here we test cursor iteration */
|
||
i = 0;
|
||
CMAP_FOR_EACH (e, node, cmap) {
|
||
assert(i < n);
|
||
cmap_values[i++] = e->value;
|
||
}
|
||
assert(i == n);
|
||
assert(e == NULL);
|
||
|
||
/* Here we test iteration with cmap_next_position() */
|
||
i = 0;
|
||
while ((node = cmap_next_position(cmap, &pos))) {
|
||
e = OBJECT_CONTAINING(node, e, node);
|
||
|
||
assert(i < n);
|
||
cmap_values2[i++] = e->value;
|
||
}
|
||
assert(i == n);
|
||
|
||
memcpy(sort_values, values, sizeof *sort_values * n);
|
||
qsort(sort_values, n, sizeof *sort_values, compare_ints);
|
||
qsort(cmap_values, n, sizeof *cmap_values, compare_ints);
|
||
qsort(cmap_values2, n, sizeof *cmap_values2, compare_ints);
|
||
|
||
for (i = 0; i < n; i++) {
|
||
assert(sort_values[i] == cmap_values[i]);
|
||
assert(sort_values[i] == cmap_values2[i]);
|
||
}
|
||
|
||
free(cmap_values2);
|
||
free(cmap_values);
|
||
free(sort_values);
|
||
|
||
/* Check that all the values are there in lookup. */
|
||
for (i = 0; i < n; i++) {
|
||
size_t count = 0;
|
||
|
||
CMAP_FOR_EACH_WITH_HASH (e, node, hash(values[i]), cmap) {
|
||
count += e->value == values[i];
|
||
}
|
||
assert(count == 1);
|
||
assert(e == NULL);
|
||
}
|
||
|
||
/* Check that all the values are there in batched lookup. */
|
||
batch_size = n % BITMAP_ULONG_BITS + 1;
|
||
for (i = 0; i < n; i += batch_size) {
|
||
unsigned long map;
|
||
uint32_t hashes[sizeof map * CHAR_BIT];
|
||
const struct cmap_node *nodes[sizeof map * CHAR_BIT];
|
||
size_t count = 0;
|
||
int k, j;
|
||
|
||
j = MIN(n - i, batch_size); /* Actual batch size. */
|
||
map = ~0UL >> (BITMAP_ULONG_BITS - j);
|
||
|
||
for (k = 0; k < j; k++) {
|
||
hashes[k] = hash(values[i + k]);
|
||
}
|
||
map = cmap_find_batch(cmap, map, hashes, nodes);
|
||
|
||
ULLONG_FOR_EACH_1(k, map) {
|
||
CMAP_NODE_FOR_EACH (e, node, nodes[k]) {
|
||
count += e->value == values[i + k];
|
||
}
|
||
assert(e == NULL);
|
||
}
|
||
assert(count == j); /* j elements in a batch. */
|
||
}
|
||
|
||
/* Check that cmap_first() returns NULL only when cmap_is_empty(). */
|
||
assert(!cmap_first(cmap) == cmap_is_empty(cmap));
|
||
|
||
/* Check counters. */
|
||
assert(cmap_is_empty(cmap) == !n);
|
||
assert(cmap_count(cmap) == n);
|
||
}
|
||
|
||
static void
|
||
shuffle(int *p, size_t n)
|
||
{
|
||
for (; n > 1; n--, p++) {
|
||
int *q = &p[random_range(n)];
|
||
int tmp = *p;
|
||
*p = *q;
|
||
*q = tmp;
|
||
}
|
||
}
|
||
|
||
/* Prints the values in 'cmap', plus 'name' as a title. */
|
||
static void OVS_UNUSED
|
||
print_cmap(const char *name, struct cmap *cmap)
|
||
{
|
||
struct cmap_cursor cursor;
|
||
struct element *e;
|
||
|
||
printf("%s:", name);
|
||
CMAP_CURSOR_FOR_EACH (e, node, &cursor, cmap) {
|
||
printf(" %d", e->value);
|
||
}
|
||
printf("\n");
|
||
}
|
||
|
||
/* Prints the 'n' values in 'values', plus 'name' as a title. */
|
||
static void OVS_UNUSED
|
||
print_ints(const char *name, const int *values, size_t n)
|
||
{
|
||
size_t i;
|
||
|
||
printf("%s:", name);
|
||
for (i = 0; i < n; i++) {
|
||
printf(" %d", values[i]);
|
||
}
|
||
printf("\n");
|
||
}
|
||
|
||
static size_t
|
||
identity_hash(int value)
|
||
{
|
||
return value;
|
||
}
|
||
|
||
static size_t
|
||
good_hash(int value)
|
||
{
|
||
return hash_int(value, 0x1234abcd);
|
||
}
|
||
|
||
static size_t
|
||
constant_hash(int value OVS_UNUSED)
|
||
{
|
||
return 123;
|
||
}
|
||
|
||
/* Tests basic cmap insertion and deletion. */
|
||
static void
|
||
test_cmap_insert_replace_delete(hash_func *hash)
|
||
{
|
||
enum { N_ELEMS = 1000 };
|
||
|
||
struct element elements[N_ELEMS];
|
||
struct element copies[N_ELEMS];
|
||
int values[N_ELEMS];
|
||
struct cmap cmap = CMAP_INITIALIZER;
|
||
size_t i;
|
||
|
||
for (i = 0; i < N_ELEMS; i++) {
|
||
elements[i].value = i;
|
||
cmap_insert(&cmap, &elements[i].node, hash(i));
|
||
values[i] = i;
|
||
check_cmap(&cmap, values, i + 1, hash);
|
||
}
|
||
shuffle(values, N_ELEMS);
|
||
for (i = 0; i < N_ELEMS; i++) {
|
||
copies[values[i]].value = values[i];
|
||
cmap_replace(&cmap, &elements[values[i]].node,
|
||
&copies[values[i]].node, hash(values[i]));
|
||
check_cmap(&cmap, values, N_ELEMS, hash);
|
||
}
|
||
shuffle(values, N_ELEMS);
|
||
for (i = 0; i < N_ELEMS; i++) {
|
||
cmap_remove(&cmap, &copies[values[i]].node, hash(values[i]));
|
||
check_cmap(&cmap, values + (i + 1), N_ELEMS - (i + 1), hash);
|
||
}
|
||
cmap_destroy(&cmap);
|
||
}
|
||
|
||
static void
|
||
run_test(void (*function)(hash_func *))
|
||
{
|
||
hash_func *hash_funcs[] = { identity_hash, good_hash, constant_hash };
|
||
size_t i;
|
||
|
||
for (i = 0; i < ARRAY_SIZE(hash_funcs); i++) {
|
||
function(hash_funcs[i]);
|
||
printf(".");
|
||
fflush(stdout);
|
||
}
|
||
}
|
||
|
||
static void
|
||
run_tests(struct ovs_cmdl_context *ctx)
|
||
{
|
||
int n;
|
||
int i;
|
||
|
||
n = ctx->argc >= 2 ? atoi(ctx->argv[1]) : 100;
|
||
for (i = 0; i < n; i++) {
|
||
run_test(test_cmap_insert_replace_delete);
|
||
}
|
||
printf("\n");
|
||
}
|
||
|
||
static int n_elems; /* Number of elements to insert. */
|
||
static int n_threads; /* Number of threads to search and mutate. */
|
||
static uint32_t mutation_frac; /* % mutations, as fraction of UINT32_MAX. */
|
||
static int n_batch; /* Number of elements in each batch. */
|
||
|
||
#define N_BATCH_MAX BITMAP_ULONG_BITS
|
||
|
||
static void benchmark_cmap(void);
|
||
static void benchmark_cmap_batched(void);
|
||
static void benchmark_hmap(void);
|
||
|
||
static int
|
||
elapsed(const struct timeval *start)
|
||
{
|
||
struct timeval end;
|
||
|
||
xgettimeofday(&end);
|
||
return timeval_to_msec(&end) - timeval_to_msec(start);
|
||
}
|
||
|
||
static void
|
||
run_benchmarks(struct ovs_cmdl_context *ctx)
|
||
{
|
||
n_elems = strtol(ctx->argv[1], NULL, 10);
|
||
n_threads = strtol(ctx->argv[2], NULL, 10);
|
||
mutation_frac = strtod(ctx->argv[3], NULL) / 100.0 * UINT32_MAX;
|
||
n_batch = ctx->argc > 4 ? strtol(ctx->argv[4], NULL, 10) : 1;
|
||
|
||
if (n_batch > N_BATCH_MAX) {
|
||
n_batch = N_BATCH_MAX;
|
||
}
|
||
printf("Benchmarking with n=%d, %d threads, %.2f%% mutations, batch size %d:\n",
|
||
n_elems, n_threads, (double) mutation_frac / UINT32_MAX * 100.,
|
||
n_batch);
|
||
|
||
if (n_batch > 0) {
|
||
benchmark_cmap_batched();
|
||
}
|
||
putchar('\n');
|
||
benchmark_cmap();
|
||
putchar('\n');
|
||
benchmark_hmap();
|
||
}
|
||
|
||
/* cmap benchmark. */
|
||
|
||
static struct element *
|
||
find(const struct cmap *cmap, int value)
|
||
{
|
||
struct element *e;
|
||
|
||
CMAP_FOR_EACH_WITH_HASH (e, node, hash_int(value, 0), cmap) {
|
||
if (e->value == value) {
|
||
return e;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
struct cmap_aux {
|
||
struct ovs_mutex mutex;
|
||
struct cmap *cmap;
|
||
};
|
||
|
||
static void *
|
||
search_cmap(void *aux_)
|
||
{
|
||
struct cmap_aux *aux = aux_;
|
||
size_t i;
|
||
|
||
if (mutation_frac) {
|
||
for (i = 0; i < n_elems; i++) {
|
||
struct element *e;
|
||
|
||
if (random_uint32() < mutation_frac) {
|
||
ovs_mutex_lock(&aux->mutex);
|
||
e = find(aux->cmap, i);
|
||
if (e) {
|
||
cmap_remove(aux->cmap, &e->node, hash_int(e->value, 0));
|
||
}
|
||
ovs_mutex_unlock(&aux->mutex);
|
||
} else {
|
||
ignore(find(aux->cmap, i));
|
||
}
|
||
}
|
||
} else {
|
||
for (i = 0; i < n_elems; i++) {
|
||
ignore(find(aux->cmap, i));
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
benchmark_cmap(void)
|
||
{
|
||
struct element *elements;
|
||
struct cmap cmap;
|
||
struct element *e;
|
||
struct timeval start;
|
||
pthread_t *threads;
|
||
struct cmap_aux aux;
|
||
size_t i;
|
||
|
||
elements = xmalloc(n_elems * sizeof *elements);
|
||
|
||
/* Insertions. */
|
||
xgettimeofday(&start);
|
||
cmap_init(&cmap);
|
||
for (i = 0; i < n_elems; i++) {
|
||
elements[i].value = i;
|
||
cmap_insert(&cmap, &elements[i].node, hash_int(i, 0));
|
||
}
|
||
printf("cmap insert: %5d ms\n", elapsed(&start));
|
||
|
||
/* Iteration. */
|
||
xgettimeofday(&start);
|
||
CMAP_FOR_EACH (e, node, &cmap) {
|
||
ignore(e);
|
||
}
|
||
printf("cmap iterate: %5d ms\n", elapsed(&start));
|
||
|
||
/* Search and mutation. */
|
||
xgettimeofday(&start);
|
||
aux.cmap = &cmap;
|
||
ovs_mutex_init(&aux.mutex);
|
||
threads = xmalloc(n_threads * sizeof *threads);
|
||
for (i = 0; i < n_threads; i++) {
|
||
threads[i] = ovs_thread_create("search", search_cmap, &aux);
|
||
}
|
||
for (i = 0; i < n_threads; i++) {
|
||
xpthread_join(threads[i], NULL);
|
||
}
|
||
free(threads);
|
||
printf("cmap search: %5d ms\n", elapsed(&start));
|
||
|
||
/* Destruction. */
|
||
xgettimeofday(&start);
|
||
CMAP_FOR_EACH (e, node, &cmap) {
|
||
cmap_remove(&cmap, &e->node, hash_int(e->value, 0));
|
||
}
|
||
cmap_destroy(&cmap);
|
||
printf("cmap destroy: %5d ms\n", elapsed(&start));
|
||
|
||
free(elements);
|
||
}
|
||
|
||
static size_t
|
||
find_batch(const struct cmap *cmap, const int value)
|
||
{
|
||
size_t i, ret;
|
||
const size_t end = MIN(n_batch, n_elems - value);
|
||
uint32_t hashes[N_BATCH_MAX];
|
||
const struct cmap_node *nodes[N_BATCH_MAX];
|
||
|
||
if (mutation_frac) {
|
||
for (i = 0; i < end; i++) {
|
||
if (random_uint32() < mutation_frac) {
|
||
break;
|
||
}
|
||
hashes[i] = hash_int(value + i, 0);
|
||
}
|
||
} else {
|
||
for (i = 0; i < end; i++) {
|
||
hashes[i] = hash_int(value + i, 0);
|
||
}
|
||
}
|
||
|
||
ret = i;
|
||
|
||
unsigned long map = i ? ~0UL >> (BITMAP_ULONG_BITS - i) : 0;
|
||
map = cmap_find_batch(cmap, map, hashes, nodes);
|
||
|
||
ULLONG_FOR_EACH_1(i, map) {
|
||
struct element *e;
|
||
|
||
CMAP_NODE_FOR_EACH (e, node, nodes[i]) {
|
||
if (OVS_LIKELY(e->value == value + i)) {
|
||
ignore(e); /* Found result. */
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
static void *
|
||
search_cmap_batched(void *aux_)
|
||
{
|
||
struct cmap_aux *aux = aux_;
|
||
size_t i = 0, j;
|
||
|
||
for (;;) {
|
||
struct element *e;
|
||
|
||
j = find_batch(aux->cmap, i);
|
||
i += j;
|
||
if (i >= n_elems) {
|
||
break;
|
||
}
|
||
if (j < n_batch) {
|
||
ovs_mutex_lock(&aux->mutex);
|
||
e = find(aux->cmap, i);
|
||
if (e) {
|
||
cmap_remove(aux->cmap, &e->node, hash_int(e->value, 0));
|
||
}
|
||
ovs_mutex_unlock(&aux->mutex);
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
benchmark_cmap_batched(void)
|
||
{
|
||
struct element *elements;
|
||
struct cmap cmap;
|
||
struct element *e;
|
||
struct timeval start;
|
||
pthread_t *threads;
|
||
struct cmap_aux aux;
|
||
size_t i;
|
||
|
||
elements = xmalloc(n_elems * sizeof *elements);
|
||
|
||
/* Insertions. */
|
||
xgettimeofday(&start);
|
||
cmap_init(&cmap);
|
||
for (i = 0; i < n_elems; i++) {
|
||
elements[i].value = i;
|
||
cmap_insert(&cmap, &elements[i].node, hash_int(i, 0));
|
||
}
|
||
printf("cmap insert: %5d ms\n", elapsed(&start));
|
||
|
||
/* Iteration. */
|
||
xgettimeofday(&start);
|
||
CMAP_FOR_EACH (e, node, &cmap) {
|
||
ignore(e);
|
||
}
|
||
printf("cmap iterate: %5d ms\n", elapsed(&start));
|
||
|
||
/* Search and mutation. */
|
||
xgettimeofday(&start);
|
||
aux.cmap = &cmap;
|
||
ovs_mutex_init(&aux.mutex);
|
||
threads = xmalloc(n_threads * sizeof *threads);
|
||
for (i = 0; i < n_threads; i++) {
|
||
threads[i] = ovs_thread_create("search", search_cmap_batched, &aux);
|
||
}
|
||
for (i = 0; i < n_threads; i++) {
|
||
xpthread_join(threads[i], NULL);
|
||
}
|
||
free(threads);
|
||
printf("batch search: %5d ms\n", elapsed(&start));
|
||
|
||
/* Destruction. */
|
||
xgettimeofday(&start);
|
||
CMAP_FOR_EACH (e, node, &cmap) {
|
||
cmap_remove(&cmap, &e->node, hash_int(e->value, 0));
|
||
}
|
||
cmap_destroy(&cmap);
|
||
printf("cmap destroy: %5d ms\n", elapsed(&start));
|
||
|
||
free(elements);
|
||
}
|
||
|
||
/* hmap benchmark. */
|
||
struct helement {
|
||
int value;
|
||
struct hmap_node node;
|
||
};
|
||
|
||
static struct helement *
|
||
hfind(const struct hmap *hmap, int value)
|
||
{
|
||
struct helement *e;
|
||
|
||
HMAP_FOR_EACH_WITH_HASH (e, node, hash_int(value, 0), hmap) {
|
||
if (e->value == value) {
|
||
return e;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
struct hmap_aux {
|
||
struct hmap *hmap;
|
||
struct fat_rwlock fatlock;
|
||
};
|
||
|
||
static void *
|
||
search_hmap(void *aux_)
|
||
{
|
||
struct hmap_aux *aux = aux_;
|
||
size_t i;
|
||
|
||
if (mutation_frac) {
|
||
for (i = 0; i < n_elems; i++) {
|
||
if (random_uint32() < mutation_frac) {
|
||
struct helement *e;
|
||
|
||
fat_rwlock_wrlock(&aux->fatlock);
|
||
e = hfind(aux->hmap, i);
|
||
if (e) {
|
||
hmap_remove(aux->hmap, &e->node);
|
||
}
|
||
fat_rwlock_unlock(&aux->fatlock);
|
||
} else {
|
||
fat_rwlock_rdlock(&aux->fatlock);
|
||
ignore(hfind(aux->hmap, i));
|
||
fat_rwlock_unlock(&aux->fatlock);
|
||
}
|
||
}
|
||
} else {
|
||
for (i = 0; i < n_elems; i++) {
|
||
ignore(hfind(aux->hmap, i));
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
benchmark_hmap(void)
|
||
{
|
||
struct helement *elements;
|
||
struct hmap hmap;
|
||
struct helement *e;
|
||
struct timeval start;
|
||
pthread_t *threads;
|
||
struct hmap_aux aux;
|
||
size_t i;
|
||
|
||
elements = xmalloc(n_elems * sizeof *elements);
|
||
|
||
xgettimeofday(&start);
|
||
hmap_init(&hmap);
|
||
for (i = 0; i < n_elems; i++) {
|
||
elements[i].value = i;
|
||
hmap_insert(&hmap, &elements[i].node, hash_int(i, 0));
|
||
}
|
||
|
||
printf("hmap insert: %5d ms\n", elapsed(&start));
|
||
|
||
xgettimeofday(&start);
|
||
HMAP_FOR_EACH (e, node, &hmap) {
|
||
ignore(e);
|
||
}
|
||
printf("hmap iterate: %5d ms\n", elapsed(&start));
|
||
|
||
xgettimeofday(&start);
|
||
aux.hmap = &hmap;
|
||
fat_rwlock_init(&aux.fatlock);
|
||
threads = xmalloc(n_threads * sizeof *threads);
|
||
for (i = 0; i < n_threads; i++) {
|
||
threads[i] = ovs_thread_create("search", search_hmap, &aux);
|
||
}
|
||
for (i = 0; i < n_threads; i++) {
|
||
xpthread_join(threads[i], NULL);
|
||
}
|
||
free(threads);
|
||
printf("hmap search: %5d ms\n", elapsed(&start));
|
||
|
||
/* Destruction. */
|
||
xgettimeofday(&start);
|
||
HMAP_FOR_EACH_SAFE (e, node, &hmap) {
|
||
hmap_remove(&hmap, &e->node);
|
||
}
|
||
hmap_destroy(&hmap);
|
||
printf("hmap destroy: %5d ms\n", elapsed(&start));
|
||
|
||
free(elements);
|
||
}
|
||
|
||
static const struct ovs_cmdl_command commands[] = {
|
||
{"check", NULL, 0, 1, run_tests, OVS_RO},
|
||
{"benchmark", NULL, 3, 4, run_benchmarks, OVS_RO},
|
||
{NULL, NULL, 0, 0, NULL, OVS_RO},
|
||
};
|
||
|
||
static void
|
||
test_cmap_main(int argc, char *argv[])
|
||
{
|
||
struct ovs_cmdl_context ctx = {
|
||
.argc = argc - optind,
|
||
.argv = argv + optind,
|
||
};
|
||
|
||
set_program_name(argv[0]);
|
||
ovs_cmdl_run_command(&ctx, commands);
|
||
}
|
||
|
||
OVSTEST_REGISTER("test-cmap", test_cmap_main);
|