[PATCH] riscv: Add optimised memcmp implementation using RVV extension
Jeffrey Law
jeffrey.law@oss.qualcomm.com
Tue Dec 30 15:30:05 GMT 2025
On 12/29/2025 11:25 PM, Zheng Ziyang wrote:
> 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
So at a high level my comments here would be similar to what we went
through with memset. Let's start with a dead-simple vector
implementation and not worry about uarch stuff yet.
Once we have a dead simple vector implementation in place, something
like what you've done may be appropriate for either a uarch specific
memcmp or perhaps we'll need some additional things we can query in
hwprobe to have a version that is perhaps not uarch specific, but is
tuned for a class of uarch designs.
For reference, here's how simple I suggest going for a generic vector
version:
/* RVV versions memcmp. RISC-V version.
Copyright (C) 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 C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <sysdep.h>
#include <sys/asm.h>
#if IS_IN(libc)
#define result a0
#define src1 a0
#define src2 a1
#define num a2
#define ivl a3
#define temp a4
#define temp1 a5
#define temp2 a6
#define ELEM_LMUL_SETTING m8
#define vdata1 v0
#define vdata2 v8
#define vmask v16
ENTRY (__memcmp_vector)
.option push
.option arch, +v
L(loop):
vsetvli ivl, num, e8, ELEM_LMUL_SETTING, ta, ma
vle8.v vdata1, (src1)
vle8.v vdata2, (src2)
vmsne.vv vmask, vdata1, vdata2
sub num, num, ivl
vfirst.m temp, vmask
/* Skip the loop if we find the different value between src1 and
src2. */
bgez temp, L(found)
add src1, src1, ivl
add src2, src2, ivl
bnez num, L(loop)
li result, 0
ret
L(found):
add src1, src1, temp
add src2, src2, temp
lbu temp1, 0(src1)
lbu temp2, 0(src2)
sub result, temp1, temp2
ret
.option pop
END (__memcmp_vector)
#endif
Note how it just does the vector loop irrespective of the size. There is
no tail to handle, there is no scalar path, etc. Just a dead-simple
vector implementation. I don't recall if this came from SiFive, RIvos
or RVI, but something along the lines of what I've posted above is what
I would expect from a uarch agnostic, generic vector memcmp.
Jeff
More information about the Libc-alpha
mailing list