Skip to contentSkip to conversation

Implement memcmp using SIMD intrinsics

PATCH In review Ravi Kerur 5 participants 10 messages May 18, 2015 – Feb 24, 2016

lore
Share & tools
lore

Ravi Kerur

author
#0 lore raw

PATCH v3 1/1 Implement memcmp using SIMD intrinsics

Background:
After preliminary discussion with John (Zhihong) and Tim from Intel it was decided that it would be beneficial to use AVX/SSE intrinsics for memcmp similar to memcpy that had been implemeneted. In addition, we decided to use librte_hash as a test candidate to test both functionality and performance.

Further discussions lead to complete functionality implementation of memory comparison and v3 code reflects that.

Test was conducted on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu 14.04, x86_64, 16GB DDR3 system.

Ravi Kerur (1):
Implement memcmp using Intel SIMD instrinsics.

 app/test/Makefile                                  |   5 +-
 app/test/autotest_data.py                          |  19 +
 app/test/test_hash_perf.c                          |  36 +-
 app/test/test_memcmp.c                             | 229 ++++++
 app/test/test_memcmp_perf.c                        | 339 ++++++++
 .../common/include/arch/ppc_64/rte_memcmp.h        |  62 ++
 .../common/include/arch/x86/rte_memcmp.h           | 900 +++++++++++++++++++++
 lib/librte_eal/common/include/generic/rte_memcmp.h | 175 ++++
 lib/librte_hash/rte_hash.c                         |  59 +-
 9 files changed, 1789 insertions(+), 35 deletions(-)
 create mode 100644 app/test/test_memcmp.c
 create mode 100644 app/test/test_memcmp_perf.c
 create mode 100644 lib/librte_eal/common/include/arch/ppc_64/rte_memcmp.h
 create mode 100644 lib/librte_eal/common/include/arch/x86/rte_memcmp.h
 create mode 100644 lib/librte_eal/common/include/generic/rte_memcmp.h
Signature · 1 line
1.9.1
author
#1 lore raw

PATCH v3 1/1 Implement memcmp using Intel SIMD instrinsics.

This patch implements memcmp and use librte_hash as the first candidate to use rte_memcmp which is implemented using AVX/SSE intrinsics.

Tested with GCC(4.8.2) and Clang(3.4-1) compilers and both tests show better performance on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu 14.04 x86_64 shows when compared to memcmp.

Changes in v3:
Implement complete memcmp functionality. Implement functional and performance tests and add it to "make test" infrastructure code.

Changes in v2:
Modified code to support only upto 64 bytes as that's the max bytes used by hash for comparison.

Changes in v1:
Initial changes to support memcmp with support upto 128 bytes.

---
 app/test/Makefile                                  |   5 +-
 app/test/autotest_data.py                          |  19 +
 app/test/test_hash_perf.c                          |  36 +-
 app/test/test_memcmp.c                             | 229 ++++++
 app/test/test_memcmp_perf.c                        | 339 ++++++++
 .../common/include/arch/ppc_64/rte_memcmp.h        |  62 ++
 .../common/include/arch/x86/rte_memcmp.h           | 900 +++++++++++++++++++++
 lib/librte_eal/common/include/generic/rte_memcmp.h | 175 ++++
 lib/librte_hash/rte_hash.c                         |  59 +-
 9 files changed, 1789 insertions(+), 35 deletions(-)
 create mode 100644 app/test/test_memcmp.c
 create mode 100644 app/test/test_memcmp_perf.c
 create mode 100644 lib/librte_eal/common/include/arch/ppc_64/rte_memcmp.h
 create mode 100644 lib/librte_eal/common/include/arch/x86/rte_memcmp.h
 create mode 100644 lib/librte_eal/common/include/generic/rte_memcmp.h
app/test/Makefile +4 -1
index 4aca77c..957e4f1 100644
@@ -81,6 +81,9 @@ SRCS-y += test_logs.c
 SRCS-y += test_memcpy.c
 SRCS-y += test_memcpy_perf.c
 
+SRCS-y += test_memcmp.c
+SRCS-y += test_memcmp_perf.c
+
 SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash.c
 SRCS-$(CONFIG_RTE_LIBRTE_HASH) += test_hash_perf.c
 
@@ -150,7 +153,7 @@ CFLAGS_test_kni.o += -Wno-deprecated-declarations
 endif
 CFLAGS += -D_GNU_SOURCE
 
-# Disable VTA for memcpy test
+# Disable VTA for memcpy tests
 ifeq ($(CC), gcc)
 ifeq ($(shell test $(GCC_VERSION) -ge 44 && echo 1), 1)
 CFLAGS_test_memcpy.o += -fno-var-tracking-assignments
app/test/autotest_data.py +19 -0
index 618a946..e07f087 100644
@@ -187,6 +187,12 @@ parallel_test_group_list = [
 		 "Report" :	None,
 		},
 		{
+		 "Name" :	"Memcmp autotest",
+		 "Command" : 	"memcmp_autotest",
+		 "Func" :	default_autotest,
+		 "Report" :	None,
+		},
+		{
 		 "Name" :	"Memzone autotest",
 		 "Command" : 	"memzone_autotest",
 		 "Func" :	default_autotest,
@@ -399,6 +405,19 @@ non_parallel_test_group_list = [
 	]
 },
 {
+	"Prefix":	"memcmp_perf",
+	"Memory" :	all_sockets(512),
+	"Tests" :
+	[
+		{
+		 "Name" :	"Memcmp performance autotest",
+		 "Command" : 	"memcmp_perf_autotest",
+		 "Func" :	default_autotest,
+		 "Report" :	None,
+		},
+	]
+},
+{
 	"Prefix":	"hash_perf",
 	"Memory" :	all_sockets(512),
 	"Tests" :	
app/test/test_hash_perf.c +22 -14
index 6eabb21..6887629 100644
@@ -440,7 +440,7 @@ run_single_tbl_perf_test(const struct rte_hash *h, hash_operation func,
 		uint32_t *invalid_pos_count)
 {
 	uint64_t begin, end, ticks = 0;
-	uint8_t *key = NULL;
+	uint8_t * volatile key = NULL;
 	uint32_t *bucket_occupancies = NULL;
 	uint32_t num_buckets, i, j;
 	int32_t pos;
@@ -547,30 +547,30 @@ run_tbl_perf_test(struct tbl_perf_test_params *params)
 	case ADD_UPDATE:
 		num_iterations = params->num_iterations;
 		params->num_iterations = params->entries;
-		run_single_tbl_perf_test(handle, rte_hash_add_key, params,
-				&avg_occupancy, &invalid_pos);
-		params->num_iterations = num_iterations;
 		ticks = run_single_tbl_perf_test(handle, rte_hash_add_key,
 				params, &avg_occupancy, &invalid_pos);
+		params->num_iterations = num_iterations;
+		ticks += run_single_tbl_perf_test(handle, rte_hash_add_key,
+				params, &avg_occupancy, &invalid_pos);
 		break;
 	case DELETE:
 		num_iterations = params->num_iterations;
 		params->num_iterations = params->entries;
-		run_single_tbl_perf_test(handle, rte_hash_add_key, params,
-				&avg_occupancy, &invalid_pos);
+		ticks = run_single_tbl_perf_test(handle, rte_hash_add_key,
+				params, &avg_occupancy, &invalid_pos);
 
 		params->num_iterations = num_iterations;
-		ticks = run_single_tbl_perf_test(handle, rte_hash_del_key,
+		ticks += run_single_tbl_perf_test(handle, rte_hash_del_key,
 				params, &avg_occupancy, &invalid_pos);
 		break;
 	case LOOKUP:
 		num_iterations = params->num_iterations;
 		params->num_iterations = params->entries;
-		run_single_tbl_perf_test(handle, rte_hash_add_key, params,
-				&avg_occupancy, &invalid_pos);
+		ticks = run_single_tbl_perf_test(handle, rte_hash_add_key,
+				params, &avg_occupancy, &invalid_pos);
 
 		params->num_iterations = num_iterations;
-		ticks = run_single_tbl_perf_test(handle, rte_hash_lookup,
+		ticks += run_single_tbl_perf_test(handle, rte_hash_lookup,
 				params, &avg_occupancy, &invalid_pos);
 		break;
 	default: return -1;
@@ -623,10 +623,15 @@ static int run_all_tbl_perf_tests(void)
 static void run_hash_func_test(rte_hash_function f, uint32_t init_val,
 		uint32_t key_len)
 {
-	static uint8_t key[RTE_HASH_KEY_LENGTH_MAX];
+	static uint8_t * volatile key;
 	uint64_t ticks = 0, start, end;
 	unsigned i, j;
 
+	key = rte_zmalloc("func hash key",
+			  key_len * sizeof(uint8_t), 16);
+	if (key == NULL)
+		return;
+
 	for (i = 0; i < HASHTEST_ITERATIONS; i++) {
 
 		for (j = 0; j < key_len; j++)
@@ -638,8 +643,11 @@ static void run_hash_func_test(rte_hash_function f, uint32_t init_val,
 		ticks += end - start;
 	}
 
-	printf("%-12s, %-18u, %-13u, %.02f\n", get_hash_name(f), (unsigned) key_len,
-			(unsigned) init_val, (double)ticks / HASHTEST_ITERATIONS);
+	rte_free(key);
+
+	printf("%-12s, %-18u, %-13u, %.02f\n",
+		get_hash_name(f), (unsigned) key_len, (unsigned) init_val,
+		(double)ticks / HASHTEST_ITERATIONS);
 }
 
 /*
@@ -687,7 +695,7 @@ fbk_hash_perf_test(void)
 		.socket_id = rte_socket_id(),
 	};
 	struct rte_fbk_hash_table *handle = NULL;
-	uint32_t *keys = NULL;
+	uint32_t * volatile keys = NULL;
 	unsigned indexes[TEST_SIZE];
 	uint64_t lookup_time = 0;
 	unsigned added = 0;
app/test/test_memcmp.c new file +229 -0

229 lines not on this page · raw message

app/test/test_memcmp_perf.c new file +339 -0

339 lines not on this page · raw message

lib/librte_eal/common/include/arch/ppc_64/rte_memcmp.h new file +62 -0

62 lines not on this page · raw message

lib/librte_eal/common/include/arch/x86/rte_memcmp.h new file +900 -0

900 lines not on this page · raw message

lib/librte_eal/common/include/generic/rte_memcmp.h new file +175 -0

175 lines not on this page · raw message

lib/librte_hash/rte_hash.c +39 -20

107 lines not on this page · raw message

Signature · 1 line
1.9.1
#2 lore raw

Re: [PATCH v3] Implement memcmp using Intel SIMD instrinsics.

Quoted 12 lines · On Mon, 18 May 2015 13:01:43 -0700 Ravi Kerur <[email protected]> wrote:

This patch implements memcmp and use librte_hash as the first candidate to use rte_memcmp which is implemented using AVX/SSE intrinsics.

Tested with GCC(4.8.2) and Clang(3.4-1) compilers and both tests show better performance on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu 14.04 x86_64 shows when compared to memcmp.

Changes in v3:
Implement complete memcmp functionality. Implement functional and performance tests and add it to "make test" infrastructure code.

Quoted 8 lines

Changes in v2:
Modified code to support only upto 64 bytes as that's the max bytes used by hash for comparison.

Changes in v1:
Initial changes to support memcmp with support upto 128 bytes.

I think this idea is best taken over to glibc not here.
The issue is that Gcc default version of memcmp inline is bad and that is what needs to be fixed.

See later discussion in email thread with Gcc intrinsic developer.

#3 lore raw

Re: [dpdk-dev, v3] Implement memcmp using Intel SIMD instrinsics.

In reply to Ravi Kerur
Quoted 2 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +0 -0

/librte_eal/common/include/arch/x86/rte_memcmp.h

[...]

lib

Quoted 7 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +7 -0
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Compare bytes between two locations. The locations must not overlap.
+ *

Parameter names should be kept consistent as they are in function body.

Quoted 15 lines

+ * @param src_1
+ * Pointer to the first source of the data.
+ * @param src_2
+ * Pointer to the second source of the data.
+ * @param n
+ * Number of bytes to compare.
+ * @return

+ *   zero if src_1 equal src_2
+ *   -ve if src_1 less than src_2
+ *   +ve if src_1 greater than src_2

+ */

lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +4 -0
+static inline int
+rte_memcmp(const void *src_1, const void *src,
+		size_t n) __attribute__((always_inline));
+

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+/**
+ * Find the first different bit for comparison.
+ */
+static inline int
+rte_cmpffd (uint32_t x, uint32_t y)
+{
+	int i;
+	int pos = x ^ y;
+	for (i = 0; i < 32; i++)
+		if (pos & (1<<i))

Coding style check :-)
BTW, does the bsf instruction provide this check?

Quoted 4 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +4 -0
+			return i;
+	return -1;
+}
+

[...]

lib

Quoted 6 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +6 -0
+/**
+ * Compare 48 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp48(const void *src_1, const void *src_2)

Guess this is not used.

[...]

lib

Quoted 11 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +11 -0
+/**
+ * Compare 256 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp256(const void *src_1, const void *src_2)
+{
+	int ret;
+
+	ret = rte_cmp64((const uint8_t *)src_1 + 0 * 64,
+			(const uint8_t *)src_2 + 0 * 64);

Why not just use rte_cmp128?

[...]

Quoted 8 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +8 -0
+static inline int
+rte_memcmp(const void *_src_1, const void *_src_2, size_t n)
+{
+	const uint8_t *src_1 = (const uint8_t *)_src_1;
+	const uint8_t *src_2 = (const uint8_t *)_src_2;
+	int ret = 0;
+
+	if (n < 16)

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+		return rte_memcmp_regular(src_1, src_2, n);
+
+	if (n <= 32) {
+		ret = rte_cmp16(src_1, src_2);
+		if (unlikely(ret != 0))
+			return ret;
+
+		return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+	}
+

Too many conditions here may harm the overall performance. It's a trade-off thing, all about balancing the overhead. Just make sure this is tuned based on actual test numbers.

Quoted 48 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +48 -0
+	if (n <= 48) {
+		ret = rte_cmp32(src_1, src_2);
+		if (unlikely(ret != 0))
+			return ret;
+
+		return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+	}
+
+	if (n <= 64) {
+		ret = rte_cmp32(src_1, src_2);
+		if (unlikely(ret != 0))
+			return ret;
+
+		ret = rte_cmp16(src_1 + 32, src_2 + 32);
+
+		if (unlikely(ret != 0))
+			return ret;
+
+		return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+	}
+
+	if (n <= 96) {
+		ret = rte_cmp64(src_1, src_2);
+		if (unlikely(ret != 0))
+			return ret;
+
+		ret = rte_cmp16(src_1 + 64, src_2 + 64);
+		if (unlikely(ret != 0))
+			return ret;
+
+		return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+	}
+
+	if (n <= 128) {
+		ret = rte_cmp64(src_1, src_2);
+		if (unlikely(ret != 0))
+			return ret;
+
+		ret = rte_cmp32(src_1 + 64, src_2 + 64);
+		if (unlikely(ret != 0))
+			return ret;
+
+		ret = rte_cmp16(src_1 + 96, src_2 + 96);
+		if (unlikely(ret != 0))
+			return ret;
+
+		return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+	}

[...]

lib

Quoted 6 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +6 -0
+/**
+ * Compare 48 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp48(const void *src_1, const void *src_2)

Not used.

Quoted 20 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +20 -0
+{
+	int ret;
+
+	ret = rte_cmp16((const uint8_t *)src_1 + 0 * 16,
+			(const uint8_t *)src_2 + 0 * 16);
+
+	if (unlikely(ret != 0))
+		return ret;
+
+	ret = rte_cmp16((const uint8_t *)src_1 + 1 * 16,
+			(const uint8_t *)src_2 + 1 * 16);
+
+	if (unlikely(ret != 0))
+		return ret;
+
+	return rte_cmp16((const uint8_t *)src_1 + 2 * 16,
+			(const uint8_t *)src_2 + 2 * 16);
+}
+
+/**

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+ * Compare 64 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp64(const void *src_1, const void *src_2)
+{
+	int ret;
+
+	ret = rte_cmp16((const uint8_t *)src_1 + 0 * 16,
+			(const uint8_t *)src_2 + 0 * 16);

Why not rte_cmp32? And use rte_cmp64 for rte_cmp128, and so on. That should make the code looks clearer.

It'd be great if you could format this patch into a patch set with several little ones. :-)
Also, the kernel checkpatch is very helpful.
Good coding style and patch organization make it easy for in-depth reviews.

author
#4 lore raw

Re: [dpdk-dev, v3] Implement memcmp using Intel SIMD instrinsics.

Quoted 51 lines · Zhihong Wang
Quoted 2 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +0 -0

/librte_eal/common/include/arch/x86/rte_memcmp.h

[...]

lib

Quoted 7 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +7 -0
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Compare bytes between two locations. The locations must not overlap.
+ *

Parameter names should be kept consistent as they are in function body.

Quoted 15 lines

+ * @param src_1
+ * Pointer to the first source of the data.
+ * @param src_2
+ * Pointer to the second source of the data.
+ * @param n
+ * Number of bytes to compare.
+ * @return

+ *   zero if src_1 equal src_2
+ *   -ve if src_1 less than src_2
+ *   +ve if src_1 greater than src_2

+ */

lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +4 -0
+static inline int
+rte_memcmp(const void *src_1, const void *src,
+             size_t n) __attribute__((always_inline));
+

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+/**
+ * Find the first different bit for comparison.
+ */
+static inline int
+rte_cmpffd (uint32_t x, uint32_t y)
+{
+     int i;
+     int pos = x ^ y;
+     for (i = 0; i < 32; i++)
+             if (pos & (1<<i))

Coding style check :-)
BTW, does the bsf instruction provide this check?

Quoted 4 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +4 -0
+                     return i;
+     return -1;
+}
+

[...]

Quoted 9 lines

lib

Quoted 6 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +6 -0
+/**
+ * Compare 48 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp48(const void *src_1, const void *src_2)

Guess this is not used.

I had left _unused_ with the assumption that it might be needed when actual performance tests are done on high end servers.

Quoted 139 lines

[...]

lib

Quoted 11 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +11 -0
+/**
+ * Compare 256 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp256(const void *src_1, const void *src_2)
+{
+     int ret;
+
+     ret = rte_cmp64((const uint8_t *)src_1 + 0 * 64,
+                     (const uint8_t *)src_2 + 0 * 64);

Why not just use rte_cmp128?

[...]

Quoted 8 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +8 -0
+static inline int
+rte_memcmp(const void *_src_1, const void *_src_2, size_t n)
+{
+     const uint8_t *src_1 = (const uint8_t *)_src_1;
+     const uint8_t *src_2 = (const uint8_t *)_src_2;
+     int ret = 0;
+
+     if (n < 16)

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+             return rte_memcmp_regular(src_1, src_2, n);
+
+     if (n <= 32) {
+             ret = rte_cmp16(src_1, src_2);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+     }
+

Too many conditions here may harm the overall performance. It's a trade-off thing, all about balancing the overhead. Just make sure this is tuned based on actual test numbers.

Quoted 48 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +48 -0
+     if (n <= 48) {
+             ret = rte_cmp32(src_1, src_2);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+     }
+
+     if (n <= 64) {
+             ret = rte_cmp32(src_1, src_2);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             ret = rte_cmp16(src_1 + 32, src_2 + 32);
+
+             if (unlikely(ret != 0))
+                     return ret;
+
+             return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+     }
+
+     if (n <= 96) {
+             ret = rte_cmp64(src_1, src_2);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             ret = rte_cmp16(src_1 + 64, src_2 + 64);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+     }
+
+     if (n <= 128) {
+             ret = rte_cmp64(src_1, src_2);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             ret = rte_cmp32(src_1 + 64, src_2 + 64);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             ret = rte_cmp16(src_1 + 96, src_2 + 96);
+             if (unlikely(ret != 0))
+                     return ret;
+
+             return rte_cmp16(src_1 - 16 + n, src_2 - 16 + n);
+     }

[...]

lib

Quoted 6 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +6 -0
+/**
+ * Compare 48 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp48(const void *src_1, const void *src_2)

Not used.

Quoted 20 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +20 -0
+{
+     int ret;
+
+     ret = rte_cmp16((const uint8_t *)src_1 + 0 * 16,
+                     (const uint8_t *)src_2 + 0 * 16);
+
+     if (unlikely(ret != 0))
+             return ret;
+
+     ret = rte_cmp16((const uint8_t *)src_1 + 1 * 16,
+                     (const uint8_t *)src_2 + 1 * 16);
+
+     if (unlikely(ret != 0))
+             return ret;
+
+     return rte_cmp16((const uint8_t *)src_1 + 2 * 16,
+                     (const uint8_t *)src_2 + 2 * 16);
+}
+
+/**

lib

Quoted 10 lines
lib renamed from lib/librte_eal/common/include/arch/x86/rte_memcmp.h +10 -0
+ * Compare 64 bytes between two locations.
+ * Locations should not overlap.
+ */
+static inline int
+rte_cmp64(const void *src_1, const void *src_2)
+{
+     int ret;
+
+     ret = rte_cmp16((const uint8_t *)src_1 + 0 * 16,
+                     (const uint8_t *)src_2 + 0 * 16);

Why not rte_cmp32? And use rte_cmp64 for rte_cmp128, and so on. That should make the code looks clearer.

Quoted 6 lines

It'd be great if you could format this patch into a patch set with several little ones. :-)
Also, the kernel checkpatch is very helpful.
Good coding style and patch organization make it easy for in-depth reviews.

Combination of scalar and vector (32/64/128) was done to get optimal performance numbers. If there is enough interest in this I can work on it and provide an updated patch set.

Thanks,
Ravi

#5 lore raw

Re: [dpdk-dev, v3] Implement memcmp using Intel SIMD instrinsics.

Quoted 6 lines
Quoted 5 lines

It'd be great if you could format this patch into a patch set with several little ones. :-)
Also, the kernel checkpatch is very helpful.
Good coding style and patch organization make it easy for in-depth reviews.

Combination of scalar and vector (32/64/128) was done to get optimal performance numbers. If there is enough interest in this I can work on it and provide an updated patch set.

That'll be very helpful! Looking forward to your patch :) BTW, have you tested real example performance with your patch?

author
#6 lore raw

Re: [dpdk-dev, v3] Implement memcmp using Intel SIMD instrinsics.

Quoted 5 lines · Zhihong
Quoted 1 line
Quoted 1 line

It'd be great if you could format this patch into a patch set with

several

Quoted 3 lines
Quoted 3 lines

little ones. :-)
Also, the kernel checkpatch is very helpful.
Good coding style and patch organization make it easy for in-depth

Quoted 9 lines

reviews.

Quoted 2 lines
Quoted 1 line

Combination of scalar and vector (32/64/128) was done to get optimal

performance numbers. If there is enough interest in this I can work on it and provide an updated patch set.

That'll be very helpful! Looking forward to your patch :) BTW, have you tested real example performance with your patch?

Yes it was tested with hash functions in dpdk code.I will work on it and send updated patch. Thanks for your inputs I will incorporate them in next patch series.

#7 lore raw
In reply to Ravi Kerur
Quoted 6 lines · Ravi Kerur

Background:
After preliminary discussion with John (Zhihong) and Tim from Intel it was decided that it would be beneficial to use AVX/SSE intrinsics for memcmp similar to memcpy that had been implemeneted. In addition, we decided to use librte_hash as a test candidate to test both functionality and performance.

Quoted 8 lines

Further discussions lead to complete functionality implementation of memory comparison and v3 code reflects that.

Test was conducted on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu 14.04, x86_64, 16GB DDR3 system.

Ravi Kerur (1):
Implement memcmp using Intel SIMD instrinsics.

As my previous mail got lost I am resending it.

In short you shouldn't
use sse2/avx2 for memcmp at all. In 95% of calls you find inequality in first 8 bytes so sse2 adds just unnecessary overhead versus checking these with.

190:   48 8b 4e 08             mov    0x8(%rsi),%rcx
194:   48 39 4f 08             cmp    %rcx,0x8(%rdi)
198:   75 f3                   jne    18d <memeq30+0xd>

Also as you have full memcmp does in your gcc optimize out if (memcmp(x,y))
like in mine?

So run also implementation below in your benchmark, my guess is it will be faster.

Original mail follows:

Hi,

I as glibc developer that wrote current strcmp code have some comments.

First is that gcc builtins for *cmp are garbage that produce rep cmpsb which is slower than byte-by-byte loop. So compile your test again with -fno-builtin-memcmp and your performance gain will probably disappear.

Then there is inlining. Its correct to do that for first 32 bytes and I plan to add header that does that check to improve performance. However not for bytes after 32'th. Thats very cold code, Only 5.6% calls reach 17th byte and 1.7% of calls read 33'th byte, so just do libcall to save size.

That also makes avx2 pointless, for most string funtions avx2 doesn't give you gains as xmm for first 64 bytes has better latency and while loop is faster its also relatively cold as its almost never reached.

For memcmp I posted on gcc list a sample implementation how it should do inlining. I found that gcc optimizes that better than expected and produces probably optimal header (see below and feel free to use it).

When you care about sign then its better to load first 8 bytes, convert them to big endian where can you compare directly. When you don't gcc managed to optimize away bswap so you check 8 bytes with three instructions below. Now I think that in header we shouldn't use sse at all.

 190:	48 8b 4e 08          	mov    0x8(%rsi),%rcx
 194:	48 39 4f 08          	cmp    %rcx,0x8(%rdi)
 198:	75 f3                	jne    18d <memeq30+0xd>

As I mentioned statistics on my computer memcmp has following:

calls 1430827
average n: 7.4 n <= 0: 0.1% n <= 4: 36.3% n <= 8: 78.4% n <=
16: 94.4% n <= 24: 97.3% n <= 32: 98.3% n <= 48: 98.6% n <= 64:
99.9%
s aligned to 4 bytes: 99.8% 8 bytes: 97.5% 16 bytes: 59.5%
average *s access cache latency 3.6 l <= 8: 92.0% l <= 16: 96.1%
l <= 32: 98.9% l <= 64: 99.4% l <= 128: 99.5%

s2 aligned to 4 bytes:  24.1%  8 bytes:  13.1% 16 bytes:   8.2% 
s-s2 aligned to 4 bytes:  24.1%  8 bytes:  15.4% 16 bytes:  10.3% 

average *s2 access cache latency 1.5 l <= 8: 98.0% l <= 16:
99.6% l <= 32: 99.9% l <= 64: 100.0% l <= 128: 100.0%
average capacity: 8.5 c <= 0: 0.0% c <= 4: 36.0% c <= 8: 78.3%
c <= 16: 91.8% c <= 24: 94.8% c <= 32: 95.7% c <= 48: 96.1% c <= 64:
99.9%

#include <string.h>
#include <stdint.h>

#undef memcmp

#define memcmp(x, y, n) (__builtin_constant_p (n) && n < 64 ? __memcmp_inline (x, y, n) \
			 : memcmp (x, y, n))

#define LOAD8(x) (*((uint8_t *) (x)))
#define LOAD32(x) (*((uint32_t *) (x)))
#define LOAD64(x) (*((uint64_t *) (x)))

#define CHECK(tp, n)
#if __BYTE_ORDER == __LITTLE_ENDIAN

# define SWAP32(x) __builtin_bswap32 (LOAD32 (x))
# define SWAP64(x) __builtin_bswap64 (LOAD64 (x))
#else
# define SWAP32(x) LOAD32 (x)
# define SWAP64(x) LOAD64 (x)
#endif

#define __ARCH_64BIT 1

static __always_inline
int
check (uint64_t x, uint64_t y)
{
if (x == y)

    return 0;
  if (x > y)
    return 1;

return -1;
}

static __always_inline
int
check_nonzero (uint64_t x, uint64_t y)
{
if (x > y)

    return 1;

return -1;
}

static __always_inline
int
__memcmp_inline (void *x, void *y, size_t n)
{

#define CHECK1 if (LOAD8 (x + i) - LOAD8 (y + i)) \
    return check_nonzero (LOAD8 (x + i), LOAD8 (y + i)); i = i + 1;
#define CHECK4 if (i == 0 ? SWAP32 (x + i) - SWAP32 (y + i)\
                      : LOAD32 (x + i) - LOAD32 (y + i)) \
    return check_nonzero (SWAP32 (x + i), SWAP32 (y + i)); i = i + 4;
#define CHECK8 if (i == 0 ? SWAP64 (x + i) - SWAP64 (y + i)\
                      : LOAD64 (x + i) - LOAD64 (y + i)) \
    return check_nonzero (SWAP64 (x + i), SWAP64 (y + i)); i = i + 8;

#define CHECK1FINAL(o) return check (LOAD8 (x + i + o), LOAD8 (y + i + o)); #define CHECK4FINAL(o) return check (SWAP32 (x + i + o), SWAP32 (y + i + o)); #define CHECK8FINAL(o) return check (SWAP64 (x + i + o), SWAP64 (y + i + o));

#if __ARCH_64BIT == 0

# undef CHECK8
# undef CHECK8FINAL
# define CHECK8 CHECK4 CHECK4
# define CHECK8FINAL(o) CHECK4 CHECK4FINAL (o)
#endif

#define LOOP if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 }

  long i = 0;

switch (n % 8)

    {
    case 0:
      if (n == 0)
	return 0;

      LOOP; CHECK8FINAL (0);
    case 1:
      LOOP CHECK1FINAL (0);
    case 2:
      if (n == 2)
	{
          CHECK1 CHECK1FINAL (0);
        }
      LOOP CHECK4FINAL (-2);
    case 3:
      if (n == 3)
	{
	  CHECK1 CHECK1 CHECK1FINAL (0);
	}
      LOOP CHECK4FINAL (-1);
    case 4:
      LOOP CHECK4FINAL (0);
    case 5:
      if (n == 5)
	{
	  CHECK4 CHECK1FINAL (0);
	}
#if __ARCH_64BIT
      LOOP CHECK8FINAL (-3);
#else
      LOOP CHECK4 CHECK1FINAL (0);
#endif
    case 6:
      if (n == 6)
	{
	  CHECK4 CHECK4FINAL (-2);
	}
      LOOP CHECK8FINAL (-2);
    case 7:
      if (n == 7)
	{
	  CHECK4 CHECK4FINAL (-1);
	}
      LOOP CHECK8FINAL (-1);
    }
}

int
memcmp1 (char *x, char *y)
{
return memcmp (x, y, 1);
}
int
memcmp10 (char *x, char *y)
{
return memcmp (x, y, 10);
}
int
memcmp20 (char *x, char *y)
{
return memcmp (x, y, 20);
}
int
memcmp30 (char *x, char *y)
{
return memcmp (x, y, 30);
}

int
memeq1 (char *x, char *y)
{
return memcmp (x, y, 1) != 0;
}
int
memeq10 (char *x, char *y)
{
return memcmp (x, y, 10) != 0;
}
int
memeq20 (char *x, char *y)
{
return memcmp (x, y, 20) != 0;
}
int
memeq30 (char *x, char *y)
{
return memcmp (x, y, 30) != 0;
}

#8 lore raw
Quoted 27 lines · Ondřej Bílka
Quoted 6 lines · Ravi Kerur

Background:
After preliminary discussion with John (Zhihong) and Tim from Intel it was decided that it would be beneficial to use AVX/SSE intrinsics for memcmp similar to memcpy that had been implemeneted. In addition, we decided to use librte_hash as a test candidate to test both functionality and performance.

Quoted 8 lines

Further discussions lead to complete functionality implementation of memory comparison and v3 code reflects that.

Test was conducted on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu 14.04, x86_64, 16GB DDR3 system.

Ravi Kerur (1):
Implement memcmp using Intel SIMD instrinsics.

As my previous mail got lost I am resending it.

In short you shouldn't
use sse2/avx2 for memcmp at all. In 95% of calls you find inequality in first 8 bytes so sse2 adds just unnecessary overhead versus checking these with.

190:   48 8b 4e 08             mov    0x8(%rsi),%rcx
194:   48 39 4f 08             cmp    %rcx,0x8(%rdi)
198:   75 f3                   jne    18d <memeq30+0xd>
Quoted 7 lines

Also as you have full memcmp does in your gcc optimize out if (memcmp(x,y))
like in mine?

So run also implementation below in your benchmark, my guess is it will be faster.

<snip for brevity>

Thanks for the contribution. It's very informative!

/Bruce

author
#9 lore raw
In reply to Ondřej Bílka
Quoted 21 lines · Ondřej Bílka
Quoted 2 lines · Ravi Kerur

Background:
After preliminary discussion with John (Zhihong) and Tim from Intel it

was

Quoted 2 lines

decided that it would be beneficial to use AVX/SSE intrinsics for memcmp similar to memcpy that had been implemeneted. In addition, we decided to

use

Quoted 1 line

librte_hash as a test candidate to test both functionality and

performance.

Quoted 2 lines

Further discussions lead to complete functionality implementation of

memory

Quoted 3 lines

comparison and v3 code reflects that.

Test was conducted on Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz, Ubuntu

14.04,

Quoted 4 lines

x86_64, 16GB DDR3 system.

Ravi Kerur (1):
Implement memcmp using Intel SIMD instrinsics.

Quoted 8 lines

As my previous mail got lost I am resending it.

In short you shouldn't
use sse2/avx2 for memcmp at all. In 95% of calls you find inequality in first 8 bytes so sse2 adds just unnecessary overhead versus checking these with.

Can you provide more details on how you found out 95% of the time inequality results within first 8 bytes and how it applies to network applications. Was any study or experiment done to understand from network applications point of view? If yes, please share it.

Secondly, we (Intel engr and I) started off with non-avx and we have slightly different version of what you have posted below for non-avx and at that time we had focussed on 128 bytes comparison only and it couldn't beat avx at all. No assumption on inequality i.e. byte difference can be anywhere from 0th to 127th byte.
snippets of code below

__inline uint16_t bswap_16(uint16_t a)
{ return __builtin_bswap16(a);}

__inline uint32_t bswap_32(uint32_t a)
{ return __builtin_bswap32(a);}

__inline uint64_t bswap_64(uint64_t a)
{ return __builtin_bswap64(a);}

#define RTE_CMP_1(a, b) { \
    uint8_t   x = *(uint8_t *)(a); \
    uint8_t    y = *(uint8_t *)(b); \
    if (x != y) return x - y; }
#define _RTE_CMP_1(a, b) \
    return *(uint8_t *)(a) - *(uint8_t *)(b);
//****************************************
#define RTE_CMP_2(a, b) { \
    uint16_t    x = bswap_16(*(uint16_t *)(a)); \
    uint16_t    y = bswap_16(*(uint16_t *)(b)); \
    if (x != y) return x - y; }
#define _RTE_CMP_2(a, b) { \
    uint16_t    x = bswap_16(*(uint16_t *)(a)); \
    uint16_t   y = bswap_16(*(uint16_t *)(b)); \
    return x - y; }
//****************************************
#define RTE_CMP_4(a, b) { \
    uint32_t    x = bswap_32(*(uint32_t *)(a)); \
    uint32_t    y = bswap_32(*(uint32_t *)(b)); \
    if (x != y) return (x < y) ? -1 : 1; }
#define _RTE_CMP_4(a, b) { \
    uint32_t    x = bswap_32(*(uint32_t *)(a)); \
    uint32_t    y = bswap_32(*(uint32_t *)(b)); \
    return (x < y) ? -1 : (x > y) ? 1 : 0; }
//****************************************
#define RTE_CMP_8(a, b) { \
    uint64_t    x = bswap_64(*(uint64_t *)(a)); \
    uint64_t    y = bswap_64(*(uint64_t *)(b)); \
    if (x != y) return (x < y) ? -1 : 1; }
#define _RTE_CMP_8(a, b) { \
    uint64_t    x = bswap_64(*(uint64_t *)(a)); \
    uint64_t    y = bswap_64(*(uint64_t *)(b)); \
    return (x < y) ? -1 : (x > y) ? 1 : 0; }

static inline int_32 rte_memcmp(const void *_a, const void *_b, size_t _size) //*************************************************************************

{
    uint8_t    *a = (uint8_t *)_a;
    uint8_t    *b = (uint8_t *)_b;
    ptrdiff_t    size = _size;
    uint64_t    x, y;
    ptrdiff_t    i;

    if (!size)
        return 0;

    RTE_CMP_1(a, b)

    if (size >= 32)
        goto cmp_long;

    for (i = 0; i <= size - 16; i += 16, a += 16, b += 16)
    {
        RTE_CMP_8(a + 0, b + 0)
        RTE_CMP_8(a + 8, b + 8)
    }

...
}

Thanks.

Quoted 239 lines
190:   48 8b 4e 08             mov    0x8(%rsi),%rcx
194:   48 39 4f 08             cmp    %rcx,0x8(%rdi)
198:   75 f3                   jne    18d <memeq30+0xd>

Also as you have full memcmp does in your gcc optimize out if (memcmp(x,y))
like in mine?

So run also implementation below in your benchmark, my guess is it will be faster.

Original mail follows:

Hi,

I as glibc developer that wrote current strcmp code have some comments.

First is that gcc builtins for *cmp are garbage that produce rep cmpsb which is slower than byte-by-byte loop. So compile your test again with -fno-builtin-memcmp and your performance gain will probably disappear.

Then there is inlining. Its correct to do that for first 32 bytes and I plan to add header that does that check to improve performance. However not for bytes after 32'th. Thats very cold code, Only 5.6% calls reach 17th byte and 1.7% of calls read 33'th byte, so just do libcall to save size.

That also makes avx2 pointless, for most string funtions avx2 doesn't give you gains as xmm for first 64 bytes has better latency and while loop is faster its also relatively cold as its almost never reached.

For memcmp I posted on gcc list a sample implementation how it should do inlining. I found that gcc optimizes that better than expected and produces probably optimal header (see below and feel free to use it).

When you care about sign then its better to load first 8 bytes, convert them to big endian where can you compare directly. When you don't gcc managed to optimize away bswap so you check 8 bytes with three instructions below. Now I think that in header we shouldn't use sse at all.

 190:   48 8b 4e 08             mov    0x8(%rsi),%rcx
 194:   48 39 4f 08             cmp    %rcx,0x8(%rdi)
 198:   75 f3                   jne    18d <memeq30+0xd>

As I mentioned statistics on my computer memcmp has following:

calls 1430827
average n: 7.4 n <= 0: 0.1% n <= 4: 36.3% n <= 8: 78.4% n <=
16: 94.4% n <= 24: 97.3% n <= 32: 98.3% n <= 48: 98.6% n <= 64:
99.9%
s aligned to 4 bytes: 99.8% 8 bytes: 97.5% 16 bytes: 59.5%
average *s access cache latency 3.6 l <= 8: 92.0% l <= 16: 96.1%
l <= 32: 98.9% l <= 64: 99.4% l <= 128: 99.5%

s2 aligned to 4 bytes:  24.1%  8 bytes:  13.1% 16 bytes:   8.2%
s-s2 aligned to 4 bytes:  24.1%  8 bytes:  15.4% 16 bytes:  10.3%

average *s2 access cache latency 1.5 l <= 8: 98.0% l <= 16:
99.6% l <= 32: 99.9% l <= 64: 100.0% l <= 128: 100.0%
average capacity: 8.5 c <= 0: 0.0% c <= 4: 36.0% c <= 8: 78.3%
c <= 16: 91.8% c <= 24: 94.8% c <= 32: 95.7% c <= 48: 96.1% c <= 64:
99.9%

#include <string.h>
#include <stdint.h>

#undef memcmp
#define memcmp(x, y, n) (__builtin_constant_p (n) && n < 64 ?

__memcmp_inline (x, y, n) \
                         : memcmp (x, y, n))

#define LOAD8(x) (*((uint8_t *) (x)))
#define LOAD32(x) (*((uint32_t *) (x)))
#define LOAD64(x) (*((uint64_t *) (x)))

#define CHECK(tp, n)
#if __BYTE_ORDER == __LITTLE_ENDIAN

# define SWAP32(x) __builtin_bswap32 (LOAD32 (x))
# define SWAP64(x) __builtin_bswap64 (LOAD64 (x))
#else
# define SWAP32(x) LOAD32 (x)
# define SWAP64(x) LOAD64 (x)
#endif

#define __ARCH_64BIT 1

static __always_inline
int
check (uint64_t x, uint64_t y)
{
if (x == y)

    return 0;
  if (x > y)
    return 1;

return -1;
}

static __always_inline
int
check_nonzero (uint64_t x, uint64_t y)
{
if (x > y)

    return 1;

return -1;
}

static __always_inline
int
__memcmp_inline (void *x, void *y, size_t n)
{

#define CHECK1 if (LOAD8 (x + i) - LOAD8 (y + i)) \
    return check_nonzero (LOAD8 (x + i), LOAD8 (y + i)); i = i + 1;
#define CHECK4 if (i == 0 ? SWAP32 (x + i) - SWAP32 (y + i)\
                      : LOAD32 (x + i) - LOAD32 (y + i)) \
    return check_nonzero (SWAP32 (x + i), SWAP32 (y + i)); i = i + 4;
#define CHECK8 if (i == 0 ? SWAP64 (x + i) - SWAP64 (y + i)\
                      : LOAD64 (x + i) - LOAD64 (y + i)) \
    return check_nonzero (SWAP64 (x + i), SWAP64 (y + i)); i = i + 8;

#define CHECK1FINAL(o) return check (LOAD8 (x + i + o), LOAD8 (y + i + o)); #define CHECK4FINAL(o) return check (SWAP32 (x + i + o), SWAP32 (y + i + o));
#define CHECK8FINAL(o) return check (SWAP64 (x + i + o), SWAP64 (y + i + o));

#if __ARCH_64BIT == 0

# undef CHECK8
# undef CHECK8FINAL
# define CHECK8 CHECK4 CHECK4
# define CHECK8FINAL(o) CHECK4 CHECK4FINAL (o)
#endif

#define LOOP if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 } \
if (i + 8 < n) { CHECK8 }

  long i = 0;

  switch (n % 8)
    {
    case 0:
      if (n == 0)
        return 0;
      LOOP; CHECK8FINAL (0);
    case 1:
      LOOP CHECK1FINAL (0);
    case 2:
      if (n == 2)
        {
          CHECK1 CHECK1FINAL (0);
        }
      LOOP CHECK4FINAL (-2);
    case 3:
      if (n == 3)
        {
          CHECK1 CHECK1 CHECK1FINAL (0);
        }
      LOOP CHECK4FINAL (-1);
    case 4:
      LOOP CHECK4FINAL (0);
    case 5:
      if (n == 5)
        {
          CHECK4 CHECK1FINAL (0);
        }
#if __ARCH_64BIT
      LOOP CHECK8FINAL (-3);
#else
      LOOP CHECK4 CHECK1FINAL (0);
#endif
    case 6:
      if (n == 6)
        {
          CHECK4 CHECK4FINAL (-2);
        }
      LOOP CHECK8FINAL (-2);
    case 7:
      if (n == 7)
        {
          CHECK4 CHECK4FINAL (-1);
        }
      LOOP CHECK8FINAL (-1);
    }
}

int
memcmp1 (char *x, char *y)
{
return memcmp (x, y, 1);
}
int
memcmp10 (char *x, char *y)
{
return memcmp (x, y, 10);
}
int
memcmp20 (char *x, char *y)
{
return memcmp (x, y, 20);
}
int
memcmp30 (char *x, char *y)
{
return memcmp (x, y, 30);
}

int
memeq1 (char *x, char *y)
{
return memcmp (x, y, 1) != 0;
}
int
memeq10 (char *x, char *y)
{
return memcmp (x, y, 10) != 0;
}
int
memeq20 (char *x, char *y)
{
return memcmp (x, y, 20) != 0;
}
int
memeq30 (char *x, char *y)
{
return memcmp (x, y, 30) != 0;
}