Implement memcmp using SIMD intrinsics
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
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
@@ -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
@@ -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
@@ -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
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_hashas the first candidate to userte_memcmpwhich 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_64shows 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.Signed-off-by Ravi Kerur <[email protected]>
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.
Re: [dpdk-dev, v3] Implement memcmp using Intel SIMD instrinsics.
In reply to Ravi KerurQuoted 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.
+ * @paramsrc_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.
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.
+ * @paramsrc_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 userte_cmp64forrte_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
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?
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.
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 uselibrte_hashas 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
intcheck_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;
}
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 uselibrte_hashas 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
Quoted 21 lines · Ondřej Bílka
Quoted 2 lines · Ravi Kerur
Background:
After preliminary discussion with John (Zhihong) and Tim from Intel itwas
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_hashas a test candidate to test both functionality andperformance.
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
#definememcmp(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_64BIT1static
__always_inline
int
check (uint64_tx,uint64_ty)
{
if (x == y)return 0; if (x > y) return 1;return -1;
}static
__always_inline
intcheck_nonzero(uint64_tx,uint64_ty)
{
if (x > y)return 1;return -1;
}static
__always_inline
int__memcmp_inline(void *x, void *y,size_tn)
{#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;
}