riscv: Implement Zbb based strlen and prefer it over the RVV based strlen implementation when Zbb is available

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Jun 11 19:51:27 GMT 2026



On 11/06/26 01:09, Jeffrey Law wrote:
> 
> So we've had Zbb variants for strlen, strcmp and a few other routines sitting here in our local repositories for a long time.  The original implementations were done by the VRULL team, then adjusted for minor bugs caught by the glibc testsuite and later wired into the hwprobe mechanism.
> 
> Much like the RVV implementations that have been dropping into the tree, I want to focus on one routine at a time to make sure we're happy with the result, then move onto the next one.  In this particular patch I'm focused on strlen.
> 
> The implementation is largely derived from the bitmanip examples, just cleaned up so that it ought to work for both rv32/rv64 and either big or little endian (little endian is untested, I believe VRULL tested rv32 at some point).
> 
> Neither the Zbb nor the RVV implementation seems at all sensitive to data alignment concerns on the K3.  So we can safely ignore that input axis and focus on how many cycles it takes to handle a string of a particular length.
> 
> I asked the LLM model to take the performance data, convert it to cycles per byte, then get the average cycles per byte over a range of lengths new buckets starting a power of 2 boundaries.
> 
> Bucket        ZBB CPB        Vector CBP     Winner
> 1-1           4.227          17.312         ZBB is ~4.1x faster
> 2-3           1.714           7.232         ZBB is ~4.2x faster
> 4-7           0.870           3.287         ZBB is ~3.8x faster
> 8-15          0.563           1.950         ZBB is ~3.5x faster
> 16-31         0.446           0.954         ZBB is ~2.1x faster
> 32-63         0.299           0.477         ZBB is ~1.6x faster
> 64-127 0.190           0.414        ZBB is ~2.2x faster
> 
> And so-on with the cycles-per-byte dropping for both, but ZBB consistently running ~2.1x faster than RVV up to a length of 8k.
> 
> 
> We can see the Zbb is just better all around.  There wasn't a single case where RVV won.  It's pretty obvious that the vector version has a higher fixed overhead, but I really expected vector to overcome that overhead as the strings got longer.  As it stands the data says quite clearly that we should be using Zbb on the K3 design and likely the K1 design (currently being tested).
> 
> Given the K1/K3 designs are what folks can get their hands on, I'd recommend we make Zbb preferred over RVV.  We'll likely have to adjust that as newer designs come into the market, but the decision should be data driven.  I'm going to run this on our Veyron V2 design and Peter is going to run on the Ascalon design, but neither of those are generally available and probably shouldn't drive decisions, those are mostly for informational purposes and to give a sense of whether or not higher targeted designs are likely to benefit from the RVV variant when those higher performance designs hit the market.
> 
> 
> You could also legitimately ask what GCC should be doing here. Right now GCC will inline the strlen call, generating RVV code that is nearly identical to what's in glibc.  So it's probably not a win for GCC to inline an RVV strlen, though inlining does at least avoid the function call overhead and allow for secondary optimization affects since there's no call.
> 
> This has been built and regression tested on the c920 and K3, the K1 is still running.  The c920 is interesting because it has neither RVV nor Zbb, so confirming I didn't do anything dumb in the resolver was useful.
Did you check the generic implementation? Both 3d6fcf1bd7f462d333c36a14efc0e03f2fdd3f9e
and 4c966c078036abe0e36bd86c9eaeb4501e552977 added zbb handling, so building with
-march=rv64imafdc_zbb -mabi=lp64d with gcc-16 I see:

$ riscv64-linux-gnu-objdump -d string/strlen-generic.os
[...]
0000000000000000 <__strlen_generic>:
   0:   ff857713                andi    a4,a0,-8
   4:   00757693                andi    a3,a0,7
   8:   0036969b                slliw   a3,a3,0x3
   c:   631c                    ld      a5,0(a4)
   e:   2877d793                orc.b   a5,a5
  12:   fff7c793                not     a5,a5
  16:   00d7d7b3                srl     a5,a5,a3
  1a:   56fd                    li      a3,-1
  1c:   e38d                    bnez    a5,3e <.L8>

000000000000001e <.L2>:
  1e:   0721                    addi    a4,a4,8
  20:   631c                    ld      a5,0(a4)
  22:   2877d793                orc.b   a5,a5
  26:   fed78ce3                beq     a5,a3,1e <.L2>
  2a:   fff7c793                not     a5,a5
  2e:   60179793                ctz     a5,a5
  32:   0037d79b                srliw   a5,a5,0x3
  36:   973e                    add     a4,a4,a5
  38:   40a70533                sub     a0,a4,a0
  3c:   8082                    ret

000000000000003e <.L8>:
  3e:   60179793                ctz     a5,a5
  42:   0037d51b                srliw   a0,a5,0x3
  46:   8082                    ret

Which seems pretty much like:

$ riscv64-linux-gnu-objdump -d string/strlen-zbb.os
[...]
0000000000000000 <__strlen_zbb-0x2>:
   0:   0001                    nop

0000000000000002 <__strlen_zbb>:
   2:   00757693                andi    a3,a0,7
   6:   ff857593                andi    a1,a0,-8
   a:   4721                    li      a4,8
   c:   8f15                    sub     a4,a4,a3
   e:   068e                    slli    a3,a3,0x3
  10:   6190                    ld      a2,0(a1)
  12:   00d65633                srl     a2,a2,a3
  16:   28765613                orc.b   a2,a2
  1a:   fff64613                not     a2,a2
  1e:   60161613                ctz     a2,a2
  22:   00365513                srli    a0,a2,0x3
  26:   02e56763                bltu    a0,a4,54 <.Ldone>
  2a:   00858693                addi    a3,a1,8
  2e:   577d                    li      a4,-1
  30:   0001                    nop
  32:   00000013                nop

0000000000000036 <.Lloop>:
  36:   6590                    ld      a2,8(a1)
  38:   05a1                    addi    a1,a1,8
  3a:   28765613                orc.b   a2,a2
  3e:   fee60ce3                beq     a2,a4,36 <.Lloop>
  42:   fff64613                not     a2,a2
  46:   60161613                ctz     a2,a2
  4a:   40d586b3                sub     a3,a1,a3
  4e:   9536                    add     a0,a0,a3
  50:   820d                    srli    a2,a2,0x3
  52:   9532                    add     a0,a0,a2

0000000000000054 <.Ldone>:
  54:   8082                    ret
  56:   0001                    nop


The ASM version seems to have an extra cost of li/sub valid-bytes setup, longer
first-word path, and the alignment padding nops.  I would guess both would have
similar performance profiles.

And there is another question if adding a zbb sysdep is really a good move here.
My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
which implies in zbb and thus you can select it without the need to add an 
specific implementation.


More information about the Libc-alpha mailing list