[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