[PATCH] riscv: Add optimised memcmp implementation using RVV extension
Zheng Ziyang
zheng.ziyang@zte.com.cn
Mon Dec 29 02:41:49 GMT 2025
From: zhengziyang <zheng.ziyang@zte.com.cn>
This patch adds an optimised memcmp implementation for RISC-V using the
RVV extension.
It dispatches based on buffer length N:
- Fully unrolled scalar path for N≤4 to avoid vector setup overhead
- Vector processing with LMUL=m2 for N>4, using masked operations for
tail handling
Key optimisation techniques:
1. Fast Mismatch Detection: Uses vmsne.vv (set if not equal) combined
with vfirst.m to instantly locate the first differing byte index
within vector registers, eliminating scalar comparison loops.
2. Efficient Vector Loop: Processes data in large chunks with LMUL=m2,
maximizing memory throughput for medium and large buffers.
3. Zero-overhead Scalar Path: For tiny buffers (1-4 bytes), bypasses
vector setup entirely with a fully unrolled linear instruction
sequence, avoiding loop control overhead (increment/compare/branch).
4. Clean Tail Handling: Remaining bytes that do not fill a full vector
are processed via a single masked vector load/compare operation,
avoiding separate tail loops.
The implementation assumes RVV 1.0 with VLEN >= 128, supports arbitrary
VLEN configurations, and works on both RV32 and RV64 platforms. No
page-size assumptions are made.
Performance improvements (relative speedup %) over __memcmp_generic
baseline:
| Test Category | Config (VLENB) | vs. __memcmp_generic |
|--------------------|-----------------------|----------------------|
| **memcmp-default** | XuanTie C920 (128) | +54.6% |
| | Spacemit(R) X60 (256) | +44.8% |
Signed-off-by: Zheng Ziyang <zheng.ziyang@zte.com.cn>
---
string/memcmp.c | 4 +-
sysdeps/riscv/multiarch/memcmp-generic.c | 26 +++
sysdeps/riscv/multiarch/memcmp_vector.S | 161 ++++++++++++++++++
.../unix/sysv/linux/riscv/multiarch/Makefile | 3 +
.../linux/riscv/multiarch/ifunc-impl-list.c | 5 +
.../unix/sysv/linux/riscv/multiarch/memcmp.c | 57 +++++++
6 files changed, 254 insertions(+), 2 deletions(-)
create mode 100644 sysdeps/riscv/multiarch/memcmp-generic.c
create mode 100644 sysdeps/riscv/multiarch/memcmp_vector.S
create mode 100644 sysdeps/unix/sysv/linux/riscv/multiarch/memcmp.c
diff --git a/string/memcmp.c b/string/memcmp.c
index cd595ce95e..5f8b0698f0 100644
--- a/string/memcmp.c
+++ b/string/memcmp.c
@@ -353,9 +353,9 @@ MEMCMP (const void *s1, const void *s2, size_t len)
libc_hidden_builtin_def(memcmp)
#ifdef weak_alias
# undef bcmp
-weak_alias (memcmp, bcmp)
+weak_alias (MEMCMP, bcmp)
#endif
#undef __memcmpeq
-strong_alias (memcmp, __memcmpeq)
+strong_alias (MEMCMP, __memcmpeq)
libc_hidden_def(__memcmpeq)
diff --git a/sysdeps/riscv/multiarch/memcmp-generic.c b/sysdeps/riscv/multiarch/memcmp-generic.c
new file mode 100644
index 0000000000..a5ddb2bec8
--- /dev/null
+++ b/sysdeps/riscv/multiarch/memcmp-generic.c
@@ -0,0 +1,26 @@
+/* Re-include the default memcpy implementation.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <string.h>
+
+#if IS_IN(libc)
+# define MEMCMP __memcmp_generic
+# undef libc_hidden_builtin_def
+# define libc_hidden_builtin_def(x)
+#endif
+#include <string/memcmp.c>
diff --git a/sysdeps/riscv/multiarch/memcmp_vector.S b/sysdeps/riscv/multiarch/memcmp_vector.S
new file mode 100644
index 0000000000..0fdbe28fd6
--- /dev/null
+++ b/sysdeps/riscv/multiarch/memcmp_vector.S
@@ -0,0 +1,161 @@
+/* memcmp for RISC-V, ignoring buffer alignment
+ Copyright (C) 2024-2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU Lesser General Public License. If not, see
+ <https://www.gnu.org/licenses/>.
+*/
+
+/* Optimised memcmp for riscv with vector extension */
+/*
+ * Core Design Logic:
+ * 1. Vector Processing: Use the Vector Extension (RVV) with LMUL=m2 to
+ * process data in large chunks, maximizing memory throughput for buffers
+ * larger than 4 bytes.
+ * 2. Fast Mismatch Detection: Utilize the 'vmsne.vv' (set if not equal) and
+ * 'vfirst.m' instructions to instantly locate the index of the first
+ * differing byte within a vector register without a scalar loop.
+ * 3. Early Scalar Threshold: For very small buffers (1 to 4 bytes), skip
+ * vector setup overhead entirely and jump to a specialized scalar path.
+ * 4. Fully Unrolled Scalar Path: The scalar handler for small sizes avoids
+ * loop control overhead (increment/compare/branch) by unrolling the
+ * comparison logic into a linear sequence of instructions.
+ * 5. Tail Handling: Remaining bytes that do not fill a full vector are
+ * handled by a single masked vector load/compare operation.
+ */
+
+#include <sysdep.h>
+
+.text
+.p2align 2
+ENTRY(__memcmp_vector)
+
+ /* Function Signature: int memcmp(const void *s1, const void *s2, size_t n)
+ * a0: const void *s1
+ * a1: const void *s2
+ * a2: size_t n
+ */
+
+ /* ----------------------------------------------------
+ * Initial Checks & Optimizations
+ * ---------------------------------------------------- */
+ mv t2, zero
+ beqz a2, .Lret_zero /* If length is 0, return 0 immediately. */
+
+ /* Small Size Optimization:
+ * If length < 5 (i.e., 1, 2, 3, 4 bytes), the overhead of configuring
+ * the vector unit (vsetvli) outweighs the benefit. Jump to the
+ * unrolled scalar path. */
+ li t1, 5
+ bltu a2, t1, .Lscalar
+
+ /* ----------------------------------------------------
+ * Vector Loop (Main Body)
+ * Processes blocks of (VLENB * 2) bytes per iteration.
+ * ---------------------------------------------------- */
+ vsetvli t1, a2, e8, m2, ta, ma
+
+1:
+ vle8.v v2, (a0) /* Load vector from s1. */
+ vle8.v v4, (a1) /* Load vector from s2. */
+ vmsne.vv v0, v2, v4 /* Compare: v0[i] = 1 if v2[i] != v4[i]. */
+ vfirst.m t3, v0 /* Find index of first mismatch (-1 if none). */
+ bgez t3, .Lvec_diff /* If t3 >= 0, a difference was found. */
+
+ /* No difference in this block. Advance pointers and counters. */
+ add a0, a0, t1
+ add a1, a1, t1
+ sub a2, a2, t1
+ bgeu a2, t1, 1b /* Continue loop if enough data remains. */
+
+ /* Check for remaining tail data. */
+ bnez a2, .Ltail
+ mv a0, zero /* No tail, fully equal. Return 0. */
+ ret
+
+ /* ----------------------------------------------------
+ * Vector Tail Handling
+ * We know a2 > 0 and a2 < current VL. Process the rest.
+ * ---------------------------------------------------- */
+.Ltail:
+ vsetvli t1, a2, e8, m2, ta, ma
+ vle8.v v2, (a0)
+ vle8.v v4, (a1)
+ vmsne.vv v0, v2, v4
+ vfirst.m t3, v0
+ bgez t3, .Lvec_diff
+ mv a0, zero /* Tail matched. Return 0. */
+ ret
+
+ /* ----------------------------------------------------
+ * Difference Calculation (Vector Path)
+ * ---------------------------------------------------- */
+.Lvec_diff:
+ /* Mismatch found at index t3 inside the vector.
+ * Calculate absolute addresses, load bytes, and return difference. */
+ add a0, a0, t3
+ lbu t0, (a0)
+ add a1, a1, t3
+ lbu t1, (a1)
+ sub a0, t0, t1 /* return (s1[i] - s2[i]) */
+ ret
+
+ /* ----------------------------------------------------
+ * Scalar Path (Fully Unrolled)
+ * Optimized for n = 1, 2, 3, 4.
+ * Eliminates loop branching penalties.
+ * ---------------------------------------------------- */
+.Lscalar:
+
+ /* Compare Byte 0 */
+ lbu t0, 0(a0)
+ addi a2, a2, -1
+ lbu t1, 0(a1)
+ bne t0, t1, .Lscalar_diff
+
+ /* Check termination for n=1 */
+ beqz a2, .Lret_zero
+
+ /* Compare Byte 1 */
+ lbu t0, 1(a0)
+ addi a2, a2, -1
+ lbu t1, 1(a1)
+ bne t0, t1, .Lscalar_diff
+
+ /* Check termination for n=2 */
+ beqz a2, .Lret_zero
+
+ /* Compare Byte 2 */
+ lbu t0, 2(a0)
+ addi a2, a2, -1
+ lbu t1, 2(a1)
+ bne t0, t1, .Lscalar_diff
+
+ /* Check termination for n=3 */
+ beqz a2, .Lret_zero
+
+ /* Compare Byte 3 (Implicitly n=4 here) */
+ lbu t0, 3(a0)
+ lbu t1, 3(a1)
+ /* Fall through to subtraction logic */
+
+.Lscalar_diff:
+ sub a0, t0, t1
+ ret
+
+.Lret_zero:
+ li a0, 0
+ ret
+
+END(__memcmp_vector)
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
index 1d26966ded..95027df6b1 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
@@ -6,6 +6,9 @@ sysdep_routines += \
memset \
memset-generic \
memset-vector \
+ memcmp \
+ memcmp-generic \
+ memcmp_vector \
# sysdep_routines
CFLAGS-memcpy_noalignment.c += -mno-strict-align
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
index 87456f3370..d5cb49aa5c 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
@@ -53,5 +53,10 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
__memset_vector)
IFUNC_IMPL_ADD (array, i, memset, 1, __memset_generic))
+ IFUNC_IMPL (i, name, memcmp,
+ IFUNC_IMPL_ADD (array, i, memcmp, rvv_enabled,
+ __memcmp_vector)
+ IFUNC_IMPL_ADD (array, i, memcmp, 1, __memcmp_generic))
+
return 0;
}
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/memcmp.c b/sysdeps/unix/sysv/linux/riscv/multiarch/memcmp.c
new file mode 100644
index 0000000000..aa7db13f1f
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/memcmp.c
@@ -0,0 +1,57 @@
+/* Multiple versions of memcpy.
+ All versions must be listed in ifunc-impl-list.c.
+ Copyright (C) 2017-2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#if IS_IN (libc)
+/* Redefine memcpy so that the compiler won't complain about the type
+ mismatch with the IFUNC selector in strong_alias, below. */
+# undef memcmp
+# define memcmp __redirect_memcmp
+# include <stdint.h>
+# include <string.h>
+# include <ifunc-init.h>
+# include <riscv-ifunc.h>
+# include <sys/hwprobe.h>
+
+extern __typeof (__redirect_memcmp) __libc_memcmp;
+
+extern __typeof (__redirect_memcmp) __memcmp_generic attribute_hidden;
+extern __typeof (__redirect_memcmp) __memcmp_vector attribute_hidden;
+
+static inline __typeof (__redirect_memcmp) *
+select_memcmp_ifunc (uint64_t dl_hwcap, __riscv_hwprobe_t hwprobe_func)
+{
+ unsigned long long int v;
+ if (__riscv_hwprobe_one (hwprobe_func, RISCV_HWPROBE_KEY_IMA_EXT_0, &v) == 0
+ && (v & RISCV_HWPROBE_IMA_V) == RISCV_HWPROBE_IMA_V)
+ return __memcmp_vector;
+
+ return __memcmp_generic;
+}
+
+riscv_libc_ifunc (__libc_memcmp, select_memcmp_ifunc);
+
+# undef memcmp
+strong_alias (__libc_memcmp, memcmp);
+# ifdef SHARED
+__hidden_ver1 (memcmp, __GI_memcmp, __redirect_memcmp)
+ __attribute__ ((visibility ("hidden"))) __attribute_copy__ (memcmp);
+# endif
+#else
+# include <string/memcmp.c>
+#endif
--
2.21.0.windows.1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20251229/2e8dc349/attachment-0001.htm>
More information about the Libc-alpha
mailing list