Proposal: Add RISC-V Optimization for XXH_mult32to64_add64 with Inline Assembly #1018
Labels
No labels
announcement
bug
build
CI
dependencies
documentation
duplicate
enhancement
experience feedback
feature request
github_actions
help wanted
install
invalid
Long-term
optimization
portability
question
target-specific
test issue
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-Cyan4973/xxHash#1018
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hi @Cyan4973,
I’ve been working on compiling xxHash as part of CouchDB on a RISC-V machine (64-bit, riscv64-unknown-linux-gnu target). While reviewing
xxhash.h, I noticed there’s no specific configuration or optimization for RISC-V, unlike the AArch64 case which usesumaddlforXXH_mult32to64_add64. Given RISC-V’s increasing relevance, I thought it might benefit from a tailored optimization.To address this, I added a RISC-V-specific version of
XXH_mult32to64_add64using inline assembly withslliandsrlito mask inputs to 32 bits, followed bymulandadd. Here’s the implementation I’ve tested:I built this within CouchDB on a RISC-V QEMU environment, disassembled the resulting
exxhash.so, and confirmed the assembly matches my intent (e.g.,slli t0, a0, 32; srli t0, t0, 32; ...appearing inXXH3_hashLong_64b_default).Before I commit this change and create a pull request, I wanted to get your thoughts:
Do you think this RISC-V optimization aligns with xxHash’s goals?
Are there specific tests or benchmarks you’d recommend I run to validate it further?
Any suggestions for refining the implementation (e.g., alternative instructions or constraints)?
I’d be happy to provide more details or adjust based on your feedback. Thanks for your time and for maintaining this great library!
Best regards,
Jay
@Cyan4973 what do you think should we go ahead with proposed changes?
Hi, @Jay-Lokhande
Since it seems Yann is busy for now, I'm asking basic question.
Do you observe actual benefit from your inline assembly implementation?
I mean that every version of RISC-V gcc (8.2.0 .. trunk) generates good code. All of them are similar to your inline assmebly version. Order of instructions are different. But total amount of latency seems same.EDIT : Sorry, it's my mistake. gcc [8.2.0 .. 13.3.0] generates different code. gcc [14.x] generates similar to your code. So it may have benefit for older version of gcc. But I'm not sure about actual performance of them.
EDIT2 : I think I understand the point of the optimization. Since it uses less temporary register (
li .. srli .. andsequence needs one more register forandmask), it may reduces register pressure and latency (for initialization of the mask value).Experiment: https://godbolt.org/z/YqzosTfTM
Output of gcc 14.x
Output of gcc [8.2.0 .. 13.3.0]
Hi, @t-mat Thank you for your suggestion. What do you think adding RISC-V support in xxHash will be beneficial would you like to give me any suggestions over it should I go ahead with the changes or would you like to suggest any benchmarking/tests over it?
First of all, since RISC-V has many variants, basically we should avoid inline-assmbly code which tend to prevent compiler's further optimization.
Actually, it seems recent compilers improved their code generator/optimizer. So, even if this patch will be merged, eventually it will be removed or applied to specifc versions of older compilers.
But this patch has some merit for older compilers. Therefore, I think we need benchmarks. It would be nice to have the result of
xxhsum's built-in benchmark. And we can compare between(1) Original (patch-less) code with older compiler
(2) This patch with older compiler
(3) Original (patch-less) code with the latest compiler
(4) This patch with the latest compiler
I suppose (2) (your patch) may be better than (1) (the original). But (2), (3) and (4) will be similar result thanks to the latest compilers.
Hi @t-mat , thanks for the feedback!
I agree that real numbers will help decide whether, and for which toolchains, we should ship this. Here’s what I think I have to do next:
Run xxhsumbenchmarks under QEMU/RISC-V for both small and large inputs (1 KB, 100 KB, 1 MB, 100 MB), using:
Guard the inline-asm behind a compiler-version check so that it only applies for older GCCs:
Does that plan sound good?
Are there any additional input sizes or flags you’d recommend including in the benchmark?
Thanks again for helping refine this!
Jay
Yes, it sounds good as a first step.
Having benchmark for small data would be nice.
For example, 16 bytes:
If you need to know the special lengthes in XXH3, please refer the following codes:
XXH3_128bits_internal()XXH3_len_0to16_128b()XXH3_MIDSIZE_MAXHi @t-mat,
Thanks again for the detailed suggestions and support throughout!
I’ve set up multiple GCC versions (9 through 14) inside the RISC-V QEMU environment.
IMAGE
I’m using a benchmarking script that iterates over these versions and runs xxhsum with the flags you recommended (-b5,11,21,25 -B16). Here's a quick look at the setup script I'm using:
I’m currently facing some issues with installing GCC-8, but I’ll try to resolve that soon for a more complete comparison. Due to ongoing college exams, I’ll need 2–3 days to complete all benchmarks and post the results table here.
Thanks again for your patience and valuable feedback!
Best regards,
Jay
Hello @t-mat,
📊 xxHash Benchmark Results (Without Patch) CLI-img
I dont have exciting results with patch which i shown in the first comment. CLI-img CLI-img2
🛠️ xxHash Benchmark Results (With Patch - Sanity Check Failed)
0x55F2F290F6F4B1910xCDEB804D65C6DEA40xCE6F2AA671ACD4600xCDEB804D65C6DEA40x68151A0F13EAEC970xCDEB804D65C6DEA40x3D208805571CAB5000xCDEB804D65C6DEA40x5B4357B5295B42180xCDEB804D65C6DEA4same for GCC-9 also
Hello @t-mat , any thoughts in it.
Before further discussion, all tests including sanity check must be passed.
Since failure of sanity check means broken calculation, please debug your patch.
Update on RISC-V implementation of XXH_mult32to64_add64:
Uses =&r to safely mark ret as early-clobber (non-overlapping with inputs),
Performs all computation in temporary registers (t0, t1), ensuring inputs are untouched,
It has passed all test cases and is compatible with GCC
I will put the comparison table soon....
Hello @t-mat,
🛠️ xxHash Benchmark Results (With Patch)
@Jay-Lokhande, thanks for your improvement.
It seems good. Could you provide more complete result for further evaluation? I mean
(1) We need the benchmark result of both of the original (without patch) version and patched version.
(2) We need different sizes. e.g. 16bytes, 1KiBytes, 1MiBytes, etc.
We need more information. Because, on my (cheap, low-end) physical 64-bit RISC-V environment, your patch makes not good result with old
gcc-10.compiler options are:
Summary of machine environment is:
Original (without patch) version of xxhash
With your patch
@t-mat ,
Thank you for your feedback and detailed benchmarks.
To clarify: my benchmarks were run on a RISC-V environment using QEMU (software emulation), not physical hardware. Is it fine?
I will put comparison table soon with and without parch with all gcc versions. also I have used this
./xxhsum -b5,11,21,25 -B16 ./xxhsum -b5,11,21,25 -B1K ./xxhsun -b5,11,21,25 -B1Mas you suggested earlier While benchmarkingIt's fine for initial testing. But it may not be fine for final evaluation.
If we won't have more benchmark of physical machines, I should oppose this patch. Because unfortunately my benchmark doesn't show any gain of it in the real world processor.
I have to agree on this with @t-mat .
Performance on
qemuis not representative of real target.FYI, there is now an
RVVimplementation ofXXH3(#1043).The vector code path is triggered for long inputs.
This is different from the proposed optimization here, focusing on
XXH_mult32to64_add64(), which is more important for short inputs.My understanding is that this investigation stopped due to lack of proper RISC-V hardware to measure the speed impact.
It is planned to resume in the future, or is it permanently closed ?
Why was this even consider reasonable?
The comments for the specialized
XXH_mult32to64_add64implementation clearly states that the only reason aarch64 has a specialized implementation is that for low performance implementations that don't have a full 64-bit hardware multiplier. In which case it uses the 32-bit -> 64-bit widening multiply add instruction. Such an instruction doesn't exist in RISC-V, there is just regular multiplication, which is fine for all relevant hardware today.Any inline assembly implementation will be worse than what the compiler generates.
I'm not sure, but AFAIK this PR has tried to bring the same optimization from newer (gcc RISC-V) compilers to older ones.
It uses fewer temporary registers, and newer compiler already emit the same instructions as proposed here.
But also from my test results, I'm leading negative on it as you mentioned.
@t-mat the codegen for the function it self isn't that important. Important is how it's integrated into the calling function, which the compiler is a lot better at if it can rearrange the instructions on it's own: https://godbolt.org/z/Pz1dW8oG5
The regular C code allows the compiler to use zext instead of slli+srli if the bitmanip extension is supported.
Also notice how I needed to add
-mno-strict-align, because RISC-V doesn't allow misaligned loads by default.If RISC-V without RVV is a target worth optimizing for (or others without unaligned load/store), then a specialized
XXH3_accumulate_512_scalarimplementation using N+1 64-bit aligned loads and combining the adjacent two loads the unaligned value would give a huge speedup.Something like (untested sketch, I probably messed up endianness):
Evaluating this using llvm-mca vs the original code compiled without the
-mno-strict-alignflag shows a 3x speedup: https://godbolt.org/z/8999K98Y4It's now only 2x slower than if the compiler is allowed to generated misaligned loads and the hardware supports them properly.
We can test for the
__riscv_misaligned_fastmacro (see RISC-V c-api docs) to check if the compiler would generates misaligned loads.@camel-cdr ,
Basically, our status of this issue is waiting for OP's investigation. If they come up with a better inline-asm solution for older compilers, that would be a great finding. If not, we will unfortunately close this issue.
If needed, please open a new issue for your evaluation so that we can avoid confusion.
Hi @t-mat, @Cyan4973, and @camel-cdr,
Thank you for the detailed feedback and testing. I've added a compiler version guard to address the concern about interfering with modern compiler optimizations.
Changes Made
The RISC-V inline assembly optimization is now only enabled for GCC < 14:
This ensures GCC 14+ can use their own optimizations without interference.
I'm unsure whether to proceed. Would you prefer to:
Close this issue , Investigate further, Keep it for specific cases (Only enable for very old GCC versions where it might help)
I understand the concerns raised about Physical hardware performance, Compiler optimization interference
dropping,
I think there is simply no equivalent to
UMADDLin risc-v instruction setwhich makes the proposed assembly replacement face an uphill battle,
effectively detrimental to performance once compared to modern compiler optimizations.