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