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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_cycles.h>
+#include <rte_random.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_tailq.h>
+#include <rte_eal.h>
+#include <rte_ip.h>
+#include <rte_string_fns.h>
+#include <cmdline_parse.h>
+
+#include <rte_tshash.h>
+#include <rte_hash.h>
+#ifdef RTE_MACHINE_CPUFLAG_SSE4_2
+#include <rte_hash_crc.h>
+#endif
+#include <rte_jhash.h>
+
+#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 <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <sys/queue.h>
+
+#include <cmdline_parse.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_atomic.h>
+#include <rte_tailq.h>
+#include <rte_eal.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_cycles.h>
+#include <rte_random.h>
+#include <rte_tshash.h>
+
+#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 <stdio.h>
+#include <inttypes.h>
+#include <rte_cycles.h>
+#include <rte_tshash.h>
+#include <rte_random.h>
+#include <rte_string_fns.h>
+#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);
