From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by dpdk.org (Postfix) with ESMTP id 2704868C2 for ; Thu, 18 Sep 2014 12:29:03 +0200 (CEST) Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by fmsmga102.fm.intel.com with ESMTP; 18 Sep 2014 03:34:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.04,546,1406617200"; d="scan'208";a="601529813" Received: from irvmail001.ir.intel.com ([163.33.26.43]) by fmsmga002.fm.intel.com with ESMTP; 18 Sep 2014 03:34:33 -0700 Received: from sivswdev02.ir.intel.com (sivswdev02.ir.intel.com [10.237.217.46]) by irvmail001.ir.intel.com (8.14.3/8.13.6/MailSET/Hub) with ESMTP id s8IAYWsp012113 for ; Thu, 18 Sep 2014 11:34:32 +0100 Received: from sivswdev02.ir.intel.com (localhost [127.0.0.1]) by sivswdev02.ir.intel.com with ESMTP id s8IAYW6F003883 for ; Thu, 18 Sep 2014 11:34:32 +0100 Received: (from pdelarax@localhost) by sivswdev02.ir.intel.com with id s8IAYWrx003879 for dev@dpdk.org; Thu, 18 Sep 2014 11:34:32 +0100 From: Pablo de Lara To: dev@dpdk.org Date: Thu, 18 Sep 2014 11:34:31 +0100 Message-Id: <1411036471-3822-4-git-send-email-pablo.de.lara.guarch@intel.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1411036471-3822-1-git-send-email-pablo.de.lara.guarch@intel.com> References: <1411036471-3822-1-git-send-email-pablo.de.lara.guarch@intel.com> Subject: [dpdk-dev] [PATCH 3/3] app/test: Added unit tests for Thread Safe Hash library X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: patches and discussions about DPDK List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 18 Sep 2014 10:29:10 -0000 Added 3 new unit tests: - Functional unit test: Tests creation and handling of a hash table with a single thread. - Performance unit tests: Benchmark hash operations add/delete/lookup, returning number of CPU cycles/operation. - Multi thread unit tests: Checks there is no data corruption due to multiple threads working on the same hash table. Signed-off-by: Pablo de Lara --- app/test/Makefile | 4 + app/test/test_tshash_func.c | 1117 +++++++++++++++++++++++++++++++++++ app/test/test_tshash_multi_thread.c | 351 +++++++++++ app/test/test_tshash_perf.c | 631 ++++++++++++++++++++ 4 files changed, 2103 insertions(+), 0 deletions(-) create mode 100644 app/test/test_tshash_func.c create mode 100644 app/test/test_tshash_multi_thread.c create mode 100644 app/test/test_tshash_perf.c diff --git a/app/test/Makefile b/app/test/Makefile index 37a3772..71dd7c2 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -83,6 +83,10 @@ SRCS-y += test_memcpy_perf.c SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c +SRCS-$(CONFIG_RTE_LIBRTE_THREAD_SAFE_HASH) += test_tshash_func.c +SRCS-$(CONFIG_RTE_LIBRTE_THREAD_SAFE_HASH) += test_tshash_perf.c +SRCS-$(CONFIG_RTE_LIBRTE_THREAD_SAFE_HASH) += test_tshash_multi_thread.c + SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm.c SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c diff --git a/app/test/test_tshash_func.c b/app/test/test_tshash_func.c new file mode 100644 index 0000000..7ec2e12 --- /dev/null +++ b/app/test/test_tshash_func.c @@ -0,0 +1,1117 @@ +/*- + * BSD LICENSE + * + * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef RTE_MACHINE_CPUFLAG_SSE4_2 +#include +#endif +#include + +#include "test.h" + +#define MAX_KEYS 1024 +#define NUM_KEYS 64 + +#if defined RTE_LIBRTE_THREAD_SAFE_HASH_STATS + +#ifdef RTE_MACHINE_CPUFLAG_SSE4_2 +static rte_hash_function hashtest_funcs[] = {rte_jhash, rte_hash_crc}; +static const char * const hash_func_strings[] = {"jhash", "crc"}; +#else +static rte_hash_function hashtest_funcs[] = {rte_jhash}; +static const char * const hash_func_strings[] = {"jhash"}; +#endif + + +/* + * Check condition and return an error if true. + */ +#define RETURN_IF_ERROR(cond, str, ...) do { \ + if (cond) { \ + printf("ERROR line %d: " str "\n", __LINE__, ##__VA_ARGS__); \ + return -1; \ + } \ +} while (0) + +/* + * Hash function that always returns the same value, to easily test what + * happens when a bucket is full. + */ +static uint32_t pseudo_hash(__attribute__((unused)) const void *keys, + __attribute__((unused)) uint32_t key_len, + __attribute__((unused)) uint32_t init_val) +{ + return 3; +} + +static void *generate_key(uint8_t num_bytes) +{ + char *key = rte_zmalloc(NULL, num_bytes, 0); + unsigned i; + + for (i = 0; i < num_bytes; i++) + key[i] = rand() % 255; + + return (void *)key; +} + +/* Parameters used for hash table in unit test functions. Name set later. */ +static struct rte_tshash_parameters { + char name[RTE_TSHASH_NAMESIZE]; + unsigned max_entries; + unsigned key_len; + uint8_t socket_id; + +} ut_params; + +static uint8_t key_sizes[] = {16, 32, 48, 64, 96, 128}; +static struct rte_tshash *hash_tables[RTE_DIM(key_sizes)]; +static uint8_t burst_sizes[] = {13, 16, 32, 64}; + +static struct rte_tshash_extra_args e_args = { + .malloc_mem = 0, + .max_load_factor = 0, + .rte_tshash_cmp_eq = NULL, + .hash_func = NULL + +}; + +static int +create_hash_tables(void) +{ + unsigned i; + for (i = 0; i < RTE_DIM(key_sizes); i++) { + hash_tables[i] = NULL; + sprintf(ut_params.name, "test_k%d", key_sizes[i]); + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = key_sizes[i]; + ut_params.socket_id = rte_socket_id(); + hash_tables[i] = rte_tshash_find_existing(ut_params.name); + if (hash_tables[i] != NULL) + rte_tshash_reset(hash_tables[i]); + else + hash_tables[i] = rte_tshash_create(ut_params.name, + ut_params.max_entries, ut_params.key_len, + ut_params.socket_id, NULL); + RETURN_IF_ERROR(hash_tables[i] == NULL, "hash creation failed"); + } + return 0; +} +/* + * Basic sequence of operations for a single key: + * - add + * - lookup (hit) + * - delete + * - lookup (miss) + */ +static int +test_hash_add_lookup_delete(void) +{ + /* Test with standard add/lookup/delete functions */ + unsigned i; + uint64_t hash_value, ret_data, data; + void *key; + + for (i = 0; i < RTE_DIM(key_sizes); i++) { + key = generate_key(key_sizes[i]); + data = rte_rand(); + + if (0 != rte_tshash_add_key(hash_tables[i], key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup(hash_tables[i], key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key(hash_tables[i], key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 == rte_tshash_lookup(hash_tables[i], key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", key_sizes[i]); + return -1; + } + + rte_free(key); + rte_tshash_reset(hash_tables[i]); + } + + /* Repeat test with precomputed hash values */ + for (i = 0; i < RTE_DIM(key_sizes); i++) { + key = generate_key(key_sizes[i]); + hash_value = rte_rand() % MAX_KEYS; + data = rte_rand(); + + if (0 != rte_tshash_add_key_with_hash(hash_tables[i], hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(hash_tables[i], hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key_with_hash(hash_tables[i], hash_value, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 == rte_tshash_lookup_with_hash(hash_tables[i], hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", key_sizes[i]); + return -1; + } + + rte_free(key); + rte_tshash_reset(hash_tables[i]); + } + + return 0; +} + +/* + * Sequence of operations for a single key: + * - delete: miss + * - add + * - lookup: hit + * - add: miss + * - lookup: hit + * - delete: hit + * - delete: miss + * - lookup: miss + */ +static int +test_hash_add_lookup_delete_miss(void) +{ + unsigned i; + uint64_t hash_value, ret_data, data; + void *key; + + for (i = 0; i < RTE_DIM(key_sizes); i++) { + key = generate_key(key_sizes[i]); + hash_value = rte_rand() % MAX_KEYS; + data = rte_rand(); + + if (0 == rte_tshash_del_key_with_hash(hash_tables[i], + hash_value, key, NULL)) { + rte_free(key); + printf("Error: Deleted key of %d bytes that should not exist\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_add_key_with_hash(hash_tables[i], + hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(hash_tables[i], + hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to lookup key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 == rte_tshash_add_key_with_hash(hash_tables[i], + hash_value, key, data)) { + rte_free(key); + printf("Error: Added key of %d bytes that already existed\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(hash_tables[i], + hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to lookup key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key_with_hash(hash_tables[i], + hash_value, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 == rte_tshash_del_key_with_hash(hash_tables[i], + hash_value, key, NULL)) { + rte_free(key); + printf("Error: Deleted key of %d bytes that should not exist\n", key_sizes[i]); + return -1; + } + + if (0 == rte_tshash_lookup_with_hash(hash_tables[i], + hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", key_sizes[i]); + return -1; + } + + rte_free(key); + rte_tshash_reset(hash_tables[i]); + } + + return 0; +} + +/* + * Sequence of operations for a single key: + * - add + * - lookup: hit + * - add: update + * - lookup: hit + * - delete: hit + * - lookup: miss + */ +static int +test_hash_add_lookup_update_delete_miss(void) +{ + struct rte_tshash *hash_tables_update[RTE_DIM(key_sizes)]; + + ut_params.max_entries = MAX_KEYS; + ut_params.socket_id = 0; + e_args.malloc_mem = 0; + e_args.max_load_factor = 0.5; + e_args.rte_tshash_cmp_eq = NULL; + e_args.hash_func = NULL; + e_args.update_add = 1; + + unsigned i; + uint64_t hash_value, ret_data, data; + void *key; + + for (i = 0; i < RTE_DIM(key_sizes); i++) { + sprintf(ut_params.name, "test_k%d_update", key_sizes[i]); + ut_params.key_len = key_sizes[i]; + + hash_tables_update[i] = rte_tshash_find_existing(ut_params.name); + if (hash_tables_update[i] != NULL) + rte_tshash_reset(hash_tables_update[i]); + else + hash_tables_update[i] = rte_tshash_create(ut_params.name, + ut_params.max_entries, + ut_params.key_len, + ut_params.socket_id, + &e_args); + RETURN_IF_ERROR(hash_tables_update[i] == NULL, "hash creation failed"); + + key = generate_key(key_sizes[i]); + hash_value = rte_rand() % MAX_KEYS; + data = rte_rand(); + + if (0 != rte_tshash_add_key_with_hash(hash_tables_update[i], + hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(hash_tables_update[i], + hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to lookup key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + data = rte_rand() % MAX_KEYS; + if (0 != rte_tshash_add_key_with_hash(hash_tables_update[i], + hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to update data of key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(hash_tables_update[i], hash_value, + key, &ret_data)) { + rte_free(key); + printf("Error: Failed to lookup key of %d bytes\n", key_sizes[i]); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key_with_hash(hash_tables_update[i], hash_value, + key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + return -1; + } + + if (0 == rte_tshash_lookup_with_hash(hash_tables_update[i], hash_value, + key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", key_sizes[i]); + return -1; + } + + rte_free(key); + rte_tshash_reset(hash_tables_update[i]); + } + + return 0; +} + +/* + * Sequence of operations for find existing hash table + * + * - find existing table: hit + * - find non-existing table: miss + * + */ +static int +test_hash_find_existing(void) +{ + struct rte_tshash *result = NULL; + unsigned i; + char test_name[RTE_TSHASH_NAMESIZE]; + + for (i = 0; i < RTE_DIM(key_sizes); i++) { + /* Try to find existing hash table */ + sprintf(test_name, "test_k%d", key_sizes[i]); + result = rte_tshash_find_existing(test_name); + RETURN_IF_ERROR(result != hash_tables[i], "could not find existing hash table"); + } + + /* Try to find non-existing hash table */ + result = rte_tshash_find_existing("hash_find_non_existing"); + RETURN_IF_ERROR(!(result == NULL), "found table that shouldn't exist"); + + return 0; +} + +/* + * Sequence of operations for 5 keys + * - add keys + * - lookup keys: hit + * - delete keys : hit + * - lookup keys: miss + */ +static int +test_hash_burst(void) +{ + unsigned i, j, k; + void *keys[NUM_KEYS]; + uint64_t data[NUM_KEYS]; + uint64_t ret_data[NUM_KEYS]; + uint64_t hash_values[NUM_KEYS]; + uint64_t *hash_values_ptrs[NUM_KEYS]; + uint64_t lookup_mask, hit_mask; + uint8_t hits; + + for (i = 0; i < RTE_DIM(key_sizes); i++) { + for (j = 0; j < NUM_KEYS; j++) { + keys[j] = generate_key(key_sizes[i]); + data[j] = rte_rand(); + if (0 != rte_tshash_add_key(hash_tables[i], keys[j], data[j])) { + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + goto fail_burst; + } + } + for (j = 0; j < RTE_DIM(burst_sizes); j++) { + if (burst_sizes[j] == 64) + lookup_mask = 0xffffffffffffffff; + else + lookup_mask = (1llu << burst_sizes[j]) - 1; + + hits = rte_tshash_lookup_bulk(hash_tables[i], lookup_mask, + (const void * const *)keys, ret_data, + &hit_mask); + if (hits != burst_sizes[j]) { + printf("Error: Failed to find %d key(s) of %d bytes\n", + burst_sizes[j] - hits, key_sizes[i]); + goto fail_burst; + } + for (k = 0; k < burst_sizes[j]; k++) { + if (unlikely(ret_data[j] != data[j])) { + printf("Error with value returned from lookup of key of %d bytes", + key_sizes[i]); + goto fail_burst; + } + } + } + + for (j = 0; j < NUM_KEYS; j++) { + if (0 != rte_tshash_del_key(hash_tables[i], keys[j], NULL)) { + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + goto fail_burst; + } + } + + lookup_mask = 0xffffffffffffffff; + hits = rte_tshash_lookup_bulk(hash_tables[i], lookup_mask, + (const void * const *)keys, ret_data, + &hit_mask); + if (0 != hits) { + printf("Error: Found %d key(s) of %d bytes that should not exist\n", + hits, key_sizes[i]); + goto fail_burst; + } + + for (j = 0; j < NUM_KEYS; j++) + rte_free(keys[j]); + rte_tshash_reset(hash_tables[i]); + } + + /* Repeat test with precomputed hash values */ + for (i = 0; i < RTE_DIM(key_sizes); i++) { + for (j = 0; j < NUM_KEYS; j++) { + keys[j] = generate_key(key_sizes[i]); + hash_values[j] = rte_rand() % MAX_KEYS; + hash_values_ptrs[j] = &hash_values[j]; + data[j] = rte_rand(); + if (0 != rte_tshash_add_key_with_hash(hash_tables[i], hash_values[j], + keys[j], data[j])) { + printf("Error: Failed to add key of %d bytes\n", key_sizes[i]); + goto fail_burst; + } + } + for (j = 0; j < RTE_DIM(burst_sizes); j++) { + if (burst_sizes[j] == 64) + lookup_mask = 0xffffffffffffffff; + else + lookup_mask = (1llu << burst_sizes[j]) - 1; + + hits = rte_tshash_lookup_bulk_with_hash(hash_tables[i], lookup_mask, + (uint64_t * const *)hash_values_ptrs, + (const void * const *)keys, ret_data, + &hit_mask); + if (hits != burst_sizes[j]) { + printf("Error: Failed to find %d key(s) of %d bytes\n", + burst_sizes[j] - hits, key_sizes[i]); + goto fail_burst; + } + for (k = 0; k < burst_sizes[j]; k++) { + if (unlikely(ret_data[j] != data[j])) { + printf("Error with value returned from lookup of key of %d bytes", + key_sizes[i]); + goto fail_burst; + } + } + } + + for (j = 0; j < NUM_KEYS; j++) { + if (0 != rte_tshash_del_key_with_hash(hash_tables[i], hash_values[j], + keys[j], NULL)) { + printf("Error: Failed to delete key of %d bytes\n", key_sizes[i]); + goto fail_burst; + return -1; + } + } + + lookup_mask = 0xffffffffffffffff; + hits = rte_tshash_lookup_bulk_with_hash(hash_tables[i], lookup_mask, + (uint64_t * const *)hash_values_ptrs, + (const void * const *)keys, ret_data, + &hit_mask); + if (0 != hits) { + printf("Error: Found %d key(s) of %d bytes that should not exist\n", + hits, key_sizes[i]); + goto fail_burst; + } + + for (j = 0; j < NUM_KEYS; j++) + rte_free(keys[j]); + rte_tshash_reset(hash_tables[i]); + } + + return 0; + +fail_burst: + for (j = 0; j < NUM_KEYS; j++) + rte_free(keys[j]); + return -1; +} + +/* + * Add keys to the same bucket until bucket full. + * - add 5 keys to the same bucket (hash created with 4 keys per bucket): + * first 4 in first level, 5th in second one + * - lookup the 5 keys: 4 hits in first level, 1 in second one + * - delete the 5 keys: 5 OK + * - lookup the 5 keys: 5 misses + */ +static int +test_hash_full_bucket(void) +{ + struct rte_tshash *handle; + unsigned i; + void *keys[NUM_KEYS]; + uint64_t data[NUM_KEYS]; + uint64_t ret_data[NUM_KEYS]; + + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = key_sizes[0]; + ut_params.socket_id = 0; + sprintf(ut_params.name, "test_full_bucket"); + e_args.max_load_factor = 0.5; + e_args.hash_func = pseudo_hash; + + handle = rte_tshash_find_existing(ut_params.name); + if (handle != NULL) + rte_tshash_reset(handle); + else + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, &e_args); + RETURN_IF_ERROR(handle == NULL, "hash creation failed"); + + + /* Fill bucket */ + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE; i++) { + keys[i] = generate_key(ut_params.key_len); + data[i] = rte_rand(); + + if (0 != rte_tshash_add_key(handle, keys[i], data[i])) { + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + goto fail_burst; + } +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(handle); + if (stats->used_slots != (i+1) || stats->num_extra_buckets != 0) { + printf("Error: used_slots = %u, expected = %u; extra_buckets = %u" + " expected = 0 \n", stats->used_slots, i+1, + stats->num_extra_buckets); + goto fail_burst; + } +#endif + } + + /* This new entry should go to the next bucket */ + keys[RTE_TSHASH_BUCKET_SIZE] = generate_key(ut_params.key_len); + data[RTE_TSHASH_BUCKET_SIZE] = rte_rand(); + + if (0 != rte_tshash_add_key(handle, keys[RTE_TSHASH_BUCKET_SIZE], + data[RTE_TSHASH_BUCKET_SIZE])) { + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + goto fail_burst; + } +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(handle); + if (stats->used_slots != (RTE_TSHASH_BUCKET_SIZE+1) + || stats->num_extra_buckets != 1) { + printf("Error: used_slots = %u, expected = %u; extra_buckets = %u" + " expected = 1 \n", stats->used_slots, RTE_TSHASH_BUCKET_SIZE+1, + stats->num_extra_buckets); + goto fail_burst; + } +#endif + + /* Lookup */ + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE+1; i++) { + if (0 != rte_tshash_lookup(handle, keys[i], &ret_data[i])) { + printf("Error: Failed to find key of %d bytes\n", ut_params.key_len); + goto fail_burst; + } + if (ret_data[i] != data[i]) { + printf("Error: Data returned was not the one expected\n"); + goto fail_burst; + } + } + + /* Delete */ + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE+1; i++) { + if (0 != rte_tshash_del_key(handle, keys[i], NULL)) { + printf("Error: Failed to delete key of %d bytes\n", ut_params.key_len); + goto fail_burst; + } +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(handle); + if (stats->used_slots != (RTE_TSHASH_BUCKET_SIZE-i) + || stats->num_extra_buckets != 1) { + printf("Error: used_slots = %u, expected = %u; extra_buckets = %u" + " expected = 1 \n", stats->used_slots, RTE_TSHASH_BUCKET_SIZE-i, + stats->num_extra_buckets); + goto fail_burst; + } +#endif + } + + /* Lookup */ + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE; i++) { + if (0 == rte_tshash_lookup(handle, keys[i], &ret_data[i])) { + printf("Error: Found key after deleting key of %d bytes\n", + ut_params.key_len); + goto fail_burst; + } + } + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE + 1; i++) + rte_free(keys[i]); + + return 0; +fail_burst: + for (i = 0; i < RTE_TSHASH_BUCKET_SIZE + 1; i++) + rte_free(keys[i]); + return -1; +} + + +/* + * Do tests for hash creation with bad parameters. + */ +static int +test_hash_creation_with_bad_parameters(void) +{ + struct rte_tshash *handle; + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = key_sizes[0]; + ut_params.socket_id = 0; + + handle = rte_tshash_create(NULL, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, NULL); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully without a name\n"); + + sprintf(ut_params.name, "creation_with_bad_parameters_0"); + ut_params.max_entries = RTE_TSHASH_MIN_ENTRIES - 1; + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, NULL); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully with maximum number of entries" + "less than minimum required\n"); + + sprintf(ut_params.name, "creation_with_bad_parameters_1"); + ut_params.max_entries = MAX_KEYS; + e_args.max_load_factor = RTE_TSHASH_MAXIMUM_LOAD_FACTOR + 0.1; + e_args.malloc_mem = 0; + e_args.rte_tshash_cmp_eq = NULL; + e_args.hash_func = NULL; + + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, &e_args); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully with max_load_factor" + "in parameter exceeded\n"); + + sprintf(ut_params.name, "creation_with_bad_parameters_2"); + e_args.max_load_factor = RTE_TSHASH_MAXIMUM_LOAD_FACTOR; + ut_params.key_len = 13; + + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, &e_args); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully wif key size is not multiple" + "of 16 and there is no user defined key compare function\n"); + + sprintf(ut_params.name, "creation_with_bad_parameters_3"); + ut_params.key_len = 0; + + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, NULL); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully if key_len in parameter is zero\n"); + + sprintf(ut_params.name, "creation_with_bad_parameters_4"); + ut_params.socket_id = RTE_MAX_NUMA_NODES; + ut_params.key_len = key_sizes[0]; + + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, NULL); + + RETURN_IF_ERROR(handle != NULL, + "Impossible creating hash sucessfully with invalid socket\n"); + + return 0; +} + +/* + * Test the creation of a hash table with malloc function. + */ +static int +test_hash_creation_with_malloc(void) +{ + struct rte_tshash *handle; + + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = key_sizes[0]; + ut_params.socket_id = 0; + sprintf(ut_params.name, "test_malloc"); + e_args.malloc_mem = 1; + e_args.max_load_factor = 0.5; + e_args.rte_tshash_cmp_eq = NULL; + e_args.hash_func = NULL; + + handle = rte_tshash_find_existing(ut_params.name); + if (handle != NULL) + rte_tshash_reset(handle); + else + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, &e_args); + RETURN_IF_ERROR(handle == NULL, "hash creation with malloc failed"); + + return 0; +} + +static int +generic_key_cmp(const void *key1, const void *key2, uint8_t key_size) +{ + return !memcmp(key1, key2, key_size); +} + +/* + * Test add/lookup/delete functions with user-defined key compare function. + */ +static int +test_hash_odd_key_size(void) +{ + struct rte_tshash *handle; + uint64_t hash_value, ret_data, data; + void *key; + + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = 26; + ut_params.socket_id = 0; + sprintf(ut_params.name, "test_odd_key_size"); + e_args.max_load_factor = 0.5; + e_args.rte_tshash_cmp_eq = generic_key_cmp; + + handle = rte_tshash_find_existing(ut_params.name); + if (handle != NULL) + rte_tshash_reset(handle); + else + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, + &e_args); + RETURN_IF_ERROR(handle == NULL, "hash creation with malloc failed"); + + /* Test with standard add/lookup/delete functions */ + key = generate_key(ut_params.key_len); + data = rte_rand(); + + if (0 != rte_tshash_add_key(handle, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 != rte_tshash_lookup(handle, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", ut_params.key_len); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key(handle, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 == rte_tshash_lookup(handle, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", + ut_params.key_len); + return -1; + } + + rte_free(key); + rte_tshash_reset(handle); + + /* Repeat test with precomputed hash values */ + key = generate_key(ut_params.key_len); + hash_value = rte_rand() % MAX_KEYS; + data = rte_rand(); + + if (0 != rte_tshash_add_key_with_hash(handle, hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(handle, hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", ut_params.key_len); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key_with_hash(handle, hash_value, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 == rte_tshash_lookup_with_hash(handle, hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", + ut_params.key_len); + return -1; + } + + rte_free(key); + rte_tshash_reset(handle); + + return 0; +} + +/* + * Test add/lookup/delete functions with different hash functions (jhash and crc). + */ +static int +test_hash_different_hash_functions(void) +{ + unsigned i; + struct rte_tshash *handle; + uint64_t hash_value, ret_data, data; + void *key; + + for (i = 0; i < RTE_DIM(hashtest_funcs); i++) { + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = key_sizes[0]; + ut_params.socket_id = 0; + sprintf(ut_params.name, "test_tshash_%s", hash_func_strings[i]); + e_args.max_load_factor = 0.5; + e_args.rte_tshash_cmp_eq = NULL; + e_args.malloc_mem = 0; + e_args.hash_func = hashtest_funcs[i]; + + handle = rte_tshash_find_existing(ut_params.name); + if (handle != NULL) + rte_tshash_reset(handle); + else + handle = rte_tshash_create(ut_params.name, ut_params.max_entries, + ut_params.key_len, ut_params.socket_id, + &e_args); + RETURN_IF_ERROR(handle == NULL, "hash creation with %s hash function failed", + hash_func_strings[i]); + + /* Test with standard add/lookup/delete functions */ + key = generate_key(ut_params.key_len); + data = rte_rand(); + + if (0 != rte_tshash_add_key(handle, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 != rte_tshash_lookup(handle, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", ut_params.key_len); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key(handle, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 == rte_tshash_lookup(handle, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", + ut_params.key_len); + return -1; + } + + rte_free(key); + rte_tshash_reset(handle); + + /* Repeat test with precomputed hash values */ + key = generate_key(ut_params.key_len); + hash_value = rte_rand() % MAX_KEYS; + data = rte_rand(); + + if (0 != rte_tshash_add_key_with_hash(handle, hash_value, key, data)) { + rte_free(key); + printf("Error: Failed to add key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 != rte_tshash_lookup_with_hash(handle, hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Failed to find key of %d bytes\n", ut_params.key_len); + return -1; + } + if (ret_data != data) { + rte_free(key); + printf("Error: Data returned was not the one expected\n"); + return -1; + } + + if (0 != rte_tshash_del_key_with_hash(handle, hash_value, key, NULL)) { + rte_free(key); + printf("Error: Failed to delete key of %d bytes\n", ut_params.key_len); + return -1; + } + + if (0 == rte_tshash_lookup_with_hash(handle, hash_value, key, &ret_data)) { + rte_free(key); + printf("Error: Found key after deleting key of %d bytes\n", ut_params.key_len); + return -1; + } + + rte_free(key); + rte_tshash_reset(handle); + } + + return 0; +} + +/* + * Do all unit tests. + */ +static int +test_tshash(void) +{ + printf("\n-- Test 0: Creating hash tables --\n"); + if (create_hash_tables() < 0) + return -1; + printf("\n-- Test1: add, lookup, delete --\n"); + if (test_hash_add_lookup_delete() < 0) + return -1; + printf("\n-- Test2: find existing hash table --\n"); + if (test_hash_find_existing() < 0) + return -1; + printf("\n-- Test3: add, lookup, delete misses --\n"); + if (test_hash_add_lookup_delete_miss() < 0) + return -1; + printf("\n-- Test4: add, lookup, update, delete misses --\n"); + if (test_hash_add_lookup_update_delete_miss() < 0) + return -1; + printf("\n-- Test5: lookup bulk --\n"); + if (test_hash_burst() < 0) + return -1; + printf("\n-- Test6: full bucket --\n"); + if (test_hash_full_bucket() < 0) + return -1; + printf("\n-- Test7: create hash table with bad parameters --\n"); + if (test_hash_creation_with_bad_parameters() < 0) + return -1; + printf("\n-- Test8: create hash table with malloc --\n"); + if (test_hash_creation_with_malloc() < 0) + return -1; + printf("\n-- Test9: add, lookup, delete with odd key size (not multiple of 16 --\n"); + if (test_hash_odd_key_size() < 0) + return -1; + printf("\n-- Test10: use different hash functions (jhash and CRC) --\n"); + if (test_hash_different_hash_functions() < 0) + return -1; + + return 0; +} +#else /* RTE_LIBRTE_THREAD_SAFE_HASH_STATS */ + +static int +test_tshash(void) +{ + printf("The Thread Safe Hash library stats are not included in this build\n"); + return 0; +} + +#endif /* RTE_LIBRTE_THREAD_SAFE_HASH_STATS */ + +static struct test_command tshash_cmd = { + .command = "thread_safe_hash_autotest", + .callback = test_tshash, +}; +REGISTER_TEST_COMMAND(tshash_cmd); + diff --git a/app/test/test_tshash_multi_thread.c b/app/test/test_tshash_multi_thread.c new file mode 100644 index 0000000..eacc5d5 --- /dev/null +++ b/app/test/test_tshash_multi_thread.c @@ -0,0 +1,351 @@ +/*- + * BSD LICENSE + * + * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" + +#define TOTAL_ADDITIONS 300000 + +#define MAX_KEYS 65536 +#define KEYSIZE 16 +#define MULTIPLIER 13 + +/* Parameters used for hash table in unit test functions. Name set later. */ +static struct hash_parameters { + char name[RTE_TSHASH_NAMESIZE]; + unsigned max_entries; + unsigned key_len; + uint8_t socket_id; +} ut_params; + +static struct ops_stats { + unsigned retries_add; + unsigned not_enough_mem_add; + unsigned already_exist_add; + unsigned no_free_slots_add; + unsigned success_add; + + unsigned retries_lookup; + unsigned errors_lookup; + unsigned misses_lookup; + unsigned success_lookup; + + unsigned num_additions; + unsigned num_deletions; + unsigned num_lookups; +} stats; + +static struct rte_tshash *hash_table; + +union hash_key { + uint64_t hashval; + char key[KEYSIZE]; +}; + +unsigned num_sec_threads; +unsigned finished_additions; + +#define RETURN_IF_ERROR(cond, str, ...) do { \ + if (cond) { \ + printf("ERROR line %d: " str "\n", __LINE__, ##__VA_ARGS__); \ + return -1; \ + } \ +} while (0) + +static int +create_hash_table(void) +{ + unsigned i; + hash_table = NULL; + sprintf(ut_params.name, "test_k%d_multi_thread", KEYSIZE); + ut_params.max_entries = MAX_KEYS; + ut_params.key_len = KEYSIZE; + ut_params.socket_id = rte_socket_id(); + + hash_table = rte_tshash_find_existing(ut_params.name); + if (hash_table != NULL) + rte_tshash_reset(hash_table); + else + hash_table = rte_tshash_create(ut_params.name, + ut_params.max_entries, ut_params.key_len, + ut_params.socket_id, NULL); + RETURN_IF_ERROR(hash_table == NULL, "hash creation failed"); + + for (i = 0; i < MAX_KEYS; i++) { + union hash_key k = { .key = {0} }; + k.hashval = i; + RETURN_IF_ERROR(rte_tshash_add_key_with_hash(hash_table, + k.hashval, (void *)k.key, k.hashval * MULTIPLIER) != 0, + "Error adding entry number %u\n", i); + } + + return 0; +} + +static int +test_add_single(__attribute__((unused)) void *arg) +{ + printf("Core %u is performing additions\n", rte_lcore_id()); + while (stats.num_additions < TOTAL_ADDITIONS) { + union hash_key k = { .key = {0} }; + k.hashval = MAX_KEYS/2-1 + (rte_rand() % 2) * MAX_KEYS/2; + if (0 == rte_tshash_add_key_with_hash(hash_table, k.hashval, + (void *)k.key, + k.hashval * MULTIPLIER)) + stats.num_additions++; + } + + printf("Core %u has finished performing additions\n", rte_lcore_id()); + finished_additions = 1; + return 0; +} + +static int +test_add_multi(__attribute__((unused)) void *arg) +{ + int ret; + unsigned i; + + printf("Core %u is performing additions\n", rte_lcore_id()); + + for (i = 0; i < MAX_KEYS; i++) { + union hash_key k = { .key = {0} }; + k.hashval = MAX_KEYS/2-1 + rte_rand() * MAX_KEYS/2; + + ret = rte_tshash_add_key_with_hash(hash_table, k.hashval, + (void *)k.key, k.hashval * MULTIPLIER); + + switch (ret) { + case -EEXIST: + __sync_fetch_and_add(&stats.already_exist_add, 1); + break; + case -EAGAIN: + __sync_fetch_and_add(&stats.retries_add, 1); + break; + case -ENOMEM: + __sync_fetch_and_add(&stats.not_enough_mem_add, 1); + break; + case -ENOSPC: + __sync_fetch_and_add(&stats.no_free_slots_add, 1); + break; + default: + __sync_fetch_and_add(&stats.success_add, 1); + __sync_fetch_and_add(&stats.num_additions, 1); + } + } + return 0; +} + +static int +test_lookup(__attribute__((unused)) void *arg) +{ + int ret; + + printf("Core %u is performing lookups\n", rte_lcore_id()); + while (finished_additions == 0) { + union hash_key k = { .key = {0} }; + uintptr_t retval = 0; + k.hashval = MAX_KEYS-1; + + ret = rte_tshash_lookup_with_hash(hash_table, k.hashval, + (void *)k.key, &retval); + __sync_fetch_and_add(&stats.num_lookups, 1); + switch (ret) { + /* Miss */ + case -1: + __sync_fetch_and_add(&stats.misses_lookup, 1); + break; + /* Out of sync */ + case -EAGAIN: + __sync_fetch_and_add(&stats.retries_lookup, 1); + break; + /* Hit */ + default: + /* Corrupted data */ + if (retval != k.hashval * MULTIPLIER) { + __sync_fetch_and_add(&stats.errors_lookup, 1); + printf("Data corrupted!\n!"); + printf("Expected %"PRIx64" but got %"PRIx64"\n", k.hashval * MULTIPLIER, retval); + } else + __sync_fetch_and_add(&stats.success_lookup, 1); + } + } + printf("Core %u has finished performing lookups\n", rte_lcore_id()); + + return 0; +} + +static int +test_delete(__attribute__((unused)) void *arg) +{ + printf("Core %u is performing deletions\n", rte_lcore_id()); + while (finished_additions == 0) { + union hash_key k = { .key = {0} }; + k.hashval = MAX_KEYS/2-1 + (rte_rand() % 2) * MAX_KEYS/2; + rte_tshash_del_key_with_hash(hash_table, k.hashval, (void *)k.key, NULL); + } + printf("Core %u has finished performing deletions\n", rte_lcore_id()); + + return 0; +} + +/* 1 thread adding entries, 1 thread deleting entries and rest looking up */ +static int +test_multi_lookup(void) { + unsigned i, j; + + do { + j = 0; + finished_additions = 0; + /* Reset operation stats */ + memset(&stats, 0, sizeof(struct ops_stats)); + + RTE_LCORE_FOREACH_SLAVE(i) { + switch (j++) { + case 0: + rte_eal_remote_launch(test_add_single, NULL, i); + break; + case 1: + rte_eal_remote_launch(test_delete, NULL, i); + break; + default: + rte_eal_remote_launch(test_lookup, NULL, i); + } + } + rte_eal_mp_wait_lcore(); + } + /* There must be at least one attempt of collision (very likely) */ + while (stats.retries_lookup == 0); + + printf("Retries on lookup op: %u/%u (%.7f%%)\n", stats.retries_lookup, + stats.num_lookups, ((double)stats.retries_lookup / stats.num_lookups * 100)); + printf("Errors on lookup op: %u/%u (%.7f%%)\n", stats.errors_lookup, + stats.num_lookups, ((double)stats.errors_lookup / stats.num_lookups * 100)); + printf("Misses on lookup op: %u/%u (%.7f%%)\n\n", stats.misses_lookup, + stats.num_lookups, ((double)stats.misses_lookup / stats.num_lookups * 100)); + printf("Successes on lookup op: %u/%u (%.7f%%)\n\n", stats.success_lookup, + stats.num_lookups, ((double)stats.success_lookup / stats.num_lookups * 100)); + + RETURN_IF_ERROR(stats.errors_lookup != 0, + "There was at least one error while looking up"); + return 0; + +} + +/* All threads (minimum 2) adding entries */ +static int +test_multi_add(void) { + unsigned i; + + do { + /* Reset operation stats */ + rte_tshash_reset(hash_table); + memset(&stats, 0, sizeof(struct ops_stats)); + + RTE_LCORE_FOREACH_SLAVE(i) { + rte_eal_remote_launch(test_add_multi, NULL, i); + } + rte_eal_mp_wait_lcore(); + } + /* There must be at least one attempt of collision (very likely) */ + while (stats.retries_add == 0); + + printf("\nRetries on add op: %u/%u (%.7f%%)\n", stats.retries_add, + (MAX_KEYS * num_sec_threads), + ((double)stats.retries_add / (MAX_KEYS * num_sec_threads) * 100)); + printf("OOM on add op: %u/%u (%.7f%%)\n", stats.not_enough_mem_add, + (MAX_KEYS * num_sec_threads), + ((double)stats.not_enough_mem_add / (MAX_KEYS * num_sec_threads) * 100)); + printf("Already existed on add op: %u/%u (%.7f%%)\n", stats.already_exist_add, + (MAX_KEYS * num_sec_threads), + ((double)stats.already_exist_add / (MAX_KEYS * num_sec_threads) * 100)); + printf("No free slots on add op: %u/%u (%.7f%%)\n\n", stats.no_free_slots_add, + (MAX_KEYS * num_sec_threads), + ((double)stats.no_free_slots_add / (MAX_KEYS * num_sec_threads) * 100)); + printf("Successes on add op: %u/%u (%.7f%%)\n\n", stats.success_add, + (MAX_KEYS * num_sec_threads), + ((double)stats.success_add / (MAX_KEYS * num_sec_threads) * 100)); + + return 0; +} + + +static int +test_tshash_multi_thread(void) +{ + + /* Master + 3 cores are needed (for add/lookup/delete) at least */ + if (rte_lcore_count() < 4) { + printf("At least 4 cores are needed\n"); + return -1; + } + num_sec_threads = rte_lcore_count() - 1; + + if (create_hash_table() < 0) + return -1; + + if (test_multi_lookup() < 0) + return -1; + + if (test_multi_add() < 0) + return -1; + + return 0; +} + +static struct test_command tshash_multi_thread_cmd = { + .command = "thread_safe_hash_multi_thread_autotest", + .callback = test_tshash_multi_thread, +}; +REGISTER_TEST_COMMAND(tshash_multi_thread_cmd); diff --git a/app/test/test_tshash_perf.c b/app/test/test_tshash_perf.c new file mode 100644 index 0000000..79235c1 --- /dev/null +++ b/app/test/test_tshash_perf.c @@ -0,0 +1,631 @@ +/*- + * BSD LICENSE + * + * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define MAX_KEYS (1<<21) +#define MULTIPLIER 13 +#define MAX_KEYSIZE 128 +#define NUM_KEYSIZES 6 +#define NUM_OPERATIONS 8 + +#define NUM_LOOKUPS (1<<24) +#define BURST_SIZE 64 + +union hash_key { + uint64_t hashval; + char key[MAX_KEYSIZE]; +}; + +unsigned key_sizes[] = {16, 32, 48, 64, 96, 128}; +struct rte_tshash *h[NUM_KEYSIZES]; +uint64_t cycles[NUM_KEYSIZES][NUM_OPERATIONS][2]; + +/* + * Set up the table so every flow fits in first level bucket + */ +static int +setup_table(unsigned with_hash, unsigned table_index) +{ + unsigned i; + char name[RTE_TSHASH_NAMESIZE]; + sprintf(name, "test_phash%d", key_sizes[table_index]); + + h[table_index] = rte_tshash_find_existing(name); + if (h[table_index] == NULL) { + h[table_index] = rte_tshash_create(name, MAX_KEYS, + key_sizes[table_index], + rte_socket_id(), NULL); + if (h[table_index] == NULL) { + printf("Error creating table\n"); + return -1; + } else { + } + } else + rte_tshash_reset(h[table_index]); + + const uint64_t start_tsc = rte_rdtsc(); + + for (i = 0; i < MAX_KEYS; i++) { + union hash_key k = { .key = {0} }; + k.hashval = i; + if (with_hash) { + if (rte_tshash_add_key_with_hash(h[table_index], k.hashval, + (const void *)k.key, + k.hashval * MULTIPLIER) != 0) { + printf("Error adding entry number %u\n", i); + return -1; + } else + continue; + } else { + if (rte_tshash_add_key(h[table_index], (const void *)k.key, + k.hashval * MULTIPLIER != 0)) { + printf("Error adding entry number %u\n", i); + return -1; + } + } + } + + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][0][with_hash] = time_taken/MAX_KEYS; + +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(h[table_index]); + printf("used_slots = %u, extra_buckets = %u\n", stats->used_slots, + stats->num_extra_buckets); +#endif + + printf("\n%"PRIu64" adds in %f seconds\n", (uint64_t)MAX_KEYS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per add\n", + cycles[table_index][0][with_hash]); + printf("Average %"PRIu64" adds per second\n", (MAX_KEYS * rte_get_tsc_hz())/time_taken); + + + return 0; +} + +/* + * Set up table so half flows are in second level buckets + */ +static int +setup_table_extd(unsigned with_hash, unsigned table_index) +{ + unsigned i, j; + char name[RTE_TSHASH_NAMESIZE]; + sprintf(name, "test_phash%d", key_sizes[table_index]); + + h[table_index] = rte_tshash_find_existing(name); + if (h[table_index] == NULL) { + h[table_index] = rte_tshash_create(name, MAX_KEYS, + key_sizes[table_index], rte_socket_id(), NULL); + if (h[table_index] == NULL) { + printf("Error creating table\n"); + return -1; + } else { + } + } else + rte_tshash_reset(h[table_index]); + + const uint64_t start_tsc = rte_rdtsc(); + if (with_hash) { + for (i = 0; i < MAX_KEYS/8; i++) { + for (j = 0; j < 8; j++) { + union hash_key k = { .key = {0} }; + k.hashval = i; + k.hashval |= (1lu<<(56+j)); + k.key[key_sizes[table_index]-1] = j; + if (rte_tshash_add_key_with_hash(h[table_index], k.hashval, + (const void *)k.key, + k.hashval * MULTIPLIER) != 0) { + printf("Error adding entry number %u\n", i*7+j); + return -1; + } + } + } + } else { + for (i = 0; i < MAX_KEYS/8; i++) { + for (j = 0; j < 8; j++) { + union hash_key k = { .key = {0} }; + k.hashval = i; + k.hashval |= (1lu<<(56+j)); + k.key[key_sizes[table_index]-1] = j; + if (rte_tshash_add_key(h[table_index], (const void *)k.key, + k.hashval * MULTIPLIER) != 0) { + printf("Error adding entry number %u\n", i*7+j); + return -1; + } + } + } + } + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][1][with_hash] = time_taken/MAX_KEYS; + +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(h[table_index]); + printf("used_slots = %u, extra_buckets = %u\n", stats->used_slots, + stats->num_extra_buckets); +#endif + + printf("\n%"PRIu64" adds in %f seconds\n", (uint64_t) MAX_KEYS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per add\n", + cycles[table_index][1][with_hash]); + printf("Average %"PRIu64" adds per second\n", + (MAX_KEYS * rte_get_tsc_hz())/time_taken); + + return 0; +} + + +static int +timed_lookups(unsigned with_hash, unsigned table_index) +{ + uint64_t i; + uintptr_t retval; + const uint64_t start_tsc = rte_rdtsc(); + + for (i = 0; i < NUM_LOOKUPS; i++) { + union hash_key k = { .key = {0} }; + + k.hashval = rte_rand() % MAX_KEYS; + if (with_hash) { + if (rte_tshash_lookup_with_hash(h[table_index], k.hashval, + (const void *)k.key, &retval) < 0) { + printf("Error lookup up hash key %"PRIu64"\n", k.hashval); + return -1; + } else + continue; + } else { + if (rte_tshash_lookup(h[table_index], + (const void *)k.key, &retval) < 0) { + printf("Error lookup up hash key %"PRIu64"\n", k.hashval); + return -1; + } + } + } + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][2][with_hash] = time_taken/NUM_LOOKUPS; + + printf("%"PRIu64" lookups in %f seconds\n", (uint64_t) NUM_LOOKUPS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per lookup\n", + cycles[table_index][2][with_hash]); + printf("Average %"PRIu64" lookups per second\n", + (NUM_LOOKUPS * rte_get_tsc_hz())/time_taken); + return 0; +} + +static int +timed_lookups_extd(unsigned with_hash, unsigned table_index) +{ + uint64_t i; + uintptr_t retval; + const uint64_t start_tsc = rte_rdtsc(); + for (i = 0; i < NUM_LOOKUPS; i++) { + union hash_key k = { .key = {0} }; + unsigned tmp = rte_rand() % 8; + k.hashval = rte_rand() % MAX_KEYS/8; + k.hashval |= (1llu << (tmp+56)); + k.key[key_sizes[table_index]-1] = tmp; + if (with_hash) { + if (rte_tshash_lookup_with_hash(h[table_index], k.hashval, + (const void *)k.key, &retval) < 0) { + printf("Error lookup up hash key %"PRIu64"\n", k.hashval); + return -1; + } else + continue; + } else { + if (rte_tshash_lookup(h[table_index], (const void *)k.key, + &retval) < 0) { + printf("Error lookup up hash key %"PRIu64"\n", k.hashval); + return -1; + } + } + } + + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][3][with_hash] = time_taken/NUM_LOOKUPS; + + printf("%"PRIu64" lookups in %f seconds\n", (uint64_t)NUM_LOOKUPS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per lookup\n", + cycles[table_index][3][with_hash]); + printf("Average %"PRIu64" lookups per second\n", + (NUM_LOOKUPS * rte_get_tsc_hz())/time_taken); + return 0; +} + +static int +timed_lookups_multi(unsigned with_hash, unsigned table_index) +{ + uint64_t i; +#if BURST_SIZE == 64 + const uint64_t lookup_mask = 0xffffffffffffffff; +#else + const uint64_t lookup_mask = (1llu << BURST_SIZE) - 1; +#endif + union hash_key ks[BURST_SIZE] = { { .key = {0} } }; + const void *keys[BURST_SIZE]; + uint64_t *h_ptrs[BURST_SIZE]; + + for (i = 0; i < BURST_SIZE; i++) { + keys[i] = (void *)ks[i].key; + h_ptrs[i] = &ks[i].hashval; + } + + const uint64_t start_tsc = rte_rdtsc(); + for (i = 0; i < NUM_LOOKUPS/BURST_SIZE; i++) { + unsigned j; + uint64_t hashes[BURST_SIZE]; + uintptr_t retvals[BURST_SIZE]; + int hitcount; + uint64_t hitmask; + + for (j = 0; j < BURST_SIZE; j++) { + hashes[j] = rte_rand() % MAX_KEYS; + ks[j].hashval = hashes[j]; + } + if (with_hash) { + hitcount = rte_tshash_lookup_bulk_with_hash(h[table_index], lookup_mask, + h_ptrs, keys, retvals, + &hitmask); + if (unlikely(hitcount != BURST_SIZE)) { + printf("Error lookup up hash keys. Expected retval = %u, got " + "%d\n", BURST_SIZE, hitcount); + printf("Hit mask = %"PRIx64"\n", hitmask); + return -1; + } else + continue; + } else { + hitcount = rte_tshash_lookup_bulk(h[table_index], lookup_mask, + keys, retvals, &hitmask); + if (unlikely(hitcount != BURST_SIZE)) { + printf("Error lookup up hash keys. Expected retval = %u, got " + "%d\n", BURST_SIZE, hitcount); + printf("Hit mask = %"PRIx64"\n", hitmask); + return -1; + } + } + } + + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][4][with_hash] = time_taken/NUM_LOOKUPS; + + printf("%"PRIu64" lookups in %f seconds\n", (uint64_t)NUM_LOOKUPS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per lookup\n", + cycles[table_index][4][with_hash]); + printf("Average %"PRIu64" lookups per second\n", + (NUM_LOOKUPS * rte_get_tsc_hz())/time_taken); + return 0; +} + +static int +timed_lookups_multi_extd(unsigned with_hash, unsigned table_index) +{ + uint64_t i; +#if BURST_SIZE == 64 + const uint64_t lookup_mask = 0xffffffffffffffff; +#else + const uint64_t lookup_mask = (1llu << BURST_SIZE) - 1; +#endif + union hash_key ks[BURST_SIZE] = { { .key = {0} } }; + const void *keys[BURST_SIZE]; + uint64_t *h_ptrs[BURST_SIZE]; + + for (i = 0; i < BURST_SIZE; i++) { + keys[i] = (void *)ks[i].key; + h_ptrs[i] = &ks[i].hashval; + } + + const uint64_t start_tsc = rte_rdtsc(); + for (i = 0; i < NUM_LOOKUPS/BURST_SIZE; i++) { + unsigned j; + uint64_t hashes[BURST_SIZE]; + uintptr_t retvals[BURST_SIZE]; + int hitcount; + uint64_t hitmask; + + for (j = 0; j < BURST_SIZE; j++) { + unsigned tmp = rte_rand() % 8; + hashes[j] = rte_rand() % MAX_KEYS/8; + hashes[j] |= (1lu << (56 + tmp)); + ks[j].hashval = hashes[j]; + ks[j].key[key_sizes[table_index] - 1] = tmp; + } + if (with_hash) { + hitcount = rte_tshash_lookup_bulk_with_hash(h[table_index], lookup_mask, + h_ptrs, keys, retvals, + &hitmask); + if (unlikely(hitcount != BURST_SIZE)) { + printf("Error lookup up hash keys. Expected retval = %u, got " + "%d\n", BURST_SIZE, hitcount); + printf("Hit mask = %"PRIx64"\n", hitmask); + return -1; + } else + continue; + } else { + hitcount = rte_tshash_lookup_bulk(h[table_index], lookup_mask, + keys, retvals, &hitmask); + if (unlikely(hitcount != BURST_SIZE)) { + printf("Error lookup up hash keys. Expected retval = %u, got " + "%d\n", BURST_SIZE, hitcount); + printf("Hit mask = %"PRIx64"\n", hitmask); + return -1; + } + } + } + + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][5][with_hash] = time_taken/NUM_LOOKUPS; + + printf("%"PRIu64" lookups in %f seconds\n", (uint64_t) NUM_LOOKUPS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per lookup\n", + cycles[table_index][5][with_hash]); + printf("Average %"PRIu64" lookups per second\n", + (NUM_LOOKUPS * rte_get_tsc_hz())/time_taken); + return 0; +} + +static int +timed_deletes(unsigned with_hash, unsigned table_index) +{ + uint64_t i; + const uint64_t start_tsc = rte_rdtsc(); + for (i = 0; i < MAX_KEYS; i++) { + union hash_key k = { .key = {0} }; + + k.hashval = i; + if (with_hash) { + if (rte_tshash_del_key_with_hash(h[table_index], k.hashval, + (const void *)k.key, NULL) < 0) { + printf("Error deleting hash key %"PRIu64"\n", k.hashval); + return -1; + } else + continue; + } else { + if (rte_tshash_del_key(h[table_index], + (const void *)k.key, NULL) < 0) { + printf("Error deleting hash key %"PRIu64"\n", k.hashval); + return -1; + } + } + + } + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][6][with_hash] = time_taken/MAX_KEYS; + +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(h[table_index]); + printf("used_slots = %u, extra_buckets = %u\n", stats->used_slots, + stats->num_extra_buckets); +#endif + + printf("\n%"PRIu64" deletions in %f seconds\n", (uint64_t) MAX_KEYS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per deletion\n", + cycles[table_index][6][with_hash]); + printf("Average %"PRIu64" deletions per second\n", + (MAX_KEYS * rte_get_tsc_hz())/time_taken); + + return 0; +} + +static int +timed_deletes_extd(unsigned with_hash, unsigned table_index) +{ + uint64_t i, j; + + const uint64_t start_tsc = rte_rdtsc(); + for (i = 0; i < MAX_KEYS/8; i++) { + for (j = 0; j < 8; j++) { + union hash_key k = { .key = {0} }; + k.hashval = i; + k.hashval |= (1lu<<(56+j)); + k.key[key_sizes[table_index]-1] = j; + if (with_hash) { + if (rte_tshash_del_key_with_hash(h[table_index], k.hashval, + (const void *)k.key, NULL) + != 0) { + printf("Error deleting hash key %"PRIu64"\n", k.hashval); + return -1; + } else + continue; + } else { + if (rte_tshash_del_key(h[table_index], (const void *)k.key, + NULL) != 0) { + printf("Error deleting hash key %"PRIu64"\n", k.hashval); + return -1; + } + } + } + } + const uint64_t end_tsc = rte_rdtsc(); + const uint64_t time_taken = end_tsc - start_tsc; + const float seconds_taken = (float)time_taken/rte_get_tsc_hz(); + cycles[table_index][7][with_hash] = time_taken/MAX_KEYS; + +#ifdef RTE_LIBRTE_THREAD_SAFE_HASH_STATS + const struct rte_tshash_stats *stats = rte_tshash_get_stats(h[table_index]); + printf("used_slots = %u, extra_buckets = %u\n", stats->used_slots, + stats->num_extra_buckets); +#endif + + printf("\n%"PRIu64" deletions in %f seconds\n", (uint64_t)MAX_KEYS, + seconds_taken); + printf("Average %"PRIu64" tsc ticks per deletion\n", + cycles[table_index][7][with_hash]); + printf("Average %"PRIu64" deletions per second\n", (MAX_KEYS * rte_get_tsc_hz())/time_taken); + + return 0; +} + +static int +reset_table(unsigned table_index) { + + rte_tshash_reset(h[table_index]); + return 0; +} + +static int +test_tshash_perf(void) +{ + unsigned i, j; + + for (i = 0; i < NUM_KEYSIZES; i++) { + printf("\n------ KEY SIZE = %u ----------\n\n", key_sizes[i]); + printf("\n ----- WITH PRECALCULATED HASH VALUES -----\n\n"); + printf("Table with only first-level buckets used\n"); + if (setup_table(1, i) < 0) + return -1; + + printf("\nTimed lookups\n"); + printf("------------------\n"); + if (timed_lookups(1, i) < 0) + return -1; + + printf("\nTimed lookups multi \n"); + printf("------------------\n"); + if (timed_lookups_multi(1, i) < 0) + return -1; + + printf("\nTimed deletions \n"); + printf("------------------\n"); + if (timed_deletes(1, i) < 0) + return -1; + + printf("\nResetting table to use extra buckets\n"); + if (setup_table_extd(1, i) < 0) + return -1; + + printf("\nTimed lookups extd\n"); + printf("------------------\n"); + if (timed_lookups_extd(1, i) < 0) + return -1; + + printf("\nTimed lookups multi extd\n"); + printf("------------------\n"); + if (timed_lookups_multi_extd(1, i) < 0) + return -1; + + printf("\nTimed deletions extd\n"); + printf("------------------\n"); + if (timed_deletes_extd(1, i) < 0) + return -1; + + reset_table(i); + + printf("\n ----- WITH JUST KEYS -----\n\n"); + + printf("Table with only first-level buckets used\n"); + if (setup_table(0, i) < 0) + return -1; + + printf("\nTimed lookups\n"); + printf("------------------\n"); + if (timed_lookups(0, i) < 0) + return -1; + + printf("\nTimed lookups multi \n"); + printf("------------------\n"); + if (timed_lookups_multi(0, i) < 0) + return -1; + + printf("\nTimed deletions \n"); + printf("------------------\n"); + if (timed_deletes(0, i) < 0) + return -1; + + reset_table(i); + + } + printf("\nResults (in CPU cycles/operation)\n"); + printf("-----------------------------------\n"); + printf("\nWith just keys (only 1st level buckets)\n"); + printf("\n%-18s%-18s%-18s%-18s%-18s\n", + "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete"); + for (i = 0; i < NUM_KEYSIZES; i++) { + printf("%-18d", key_sizes[i]); + for (j = 0; j < 8; j = j+2) + printf("%-18"PRIu64, cycles[i][j][0]); + printf("\n"); + } + + printf("\nWith precalculated hash (only 1st level buckets)\n"); + printf("\n%-18s%-18s%-18s%-18s%-18s\n", + "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete"); + for (i = 0; i < NUM_KEYSIZES; i++) { + printf("%-18d", key_sizes[i]); + for (j = 0; j < 8; j = j+2) + printf("%-18"PRIu64, cycles[i][j][1]); + printf("\n"); + } + printf("\nWith precalculated hash (with extended buckets)\n"); + printf("\n%-18s%-18s%-18s%-18s%-18s\n", + "Keysize", "Add", "Lookup", "Lookup_bulk", "Delete"); + for (i = 0; i < NUM_KEYSIZES; i++) { + printf("%-18d", key_sizes[i]); + for (j = 1; j < 8; j = j+2) + printf("%-18"PRIu64, cycles[i][j][1]); + printf("\n"); + } + return 0; +} + +static struct test_command tshash_perf_cmd = { + .command = "thread_safe_hash_perf_autotest", + .callback = test_tshash_perf, +}; +REGISTER_TEST_COMMAND(tshash_perf_cmd); -- 1.7.7.6