Optimize Streebog compression scheduling on ARM64#5653
Conversation
|
Have you benchmarked this on other machines? Likely most superscalar processors would benefit from this kind of interleaving and there is nothing really ARM specific here aiui |
|
I have ARM64 and x64 machines here, those are the two I tested on. No doubt there are other architectures that could benefit, but that's difficult for me to test. The basic idea is keeping the ARM instruction pipeline busy, not sure how that translates to other platforms. So the PR is defensive, I didn't want to introduce regressions for untested platforms. |
|
I missed the comment in the patch that this regressed on x86. And yes you're probably right that it is due to register pressure. This situation is also a bit different from prior cases where interleaving improved performance across the board, since the cost here is probably ~90% cache bandwidth. Typically prior cases had a lot of basic arithmetic operations (add, xor, etc) with a short critical path, so unrolling by 2 or 4x increased available ILP Out of curiosity how critical is Streebog performance for your use case? (On ARM or elsewhere) Likely it could be optimized significantly using NEON, AVX-512, etc, but it's not one that I've looked at much. |
Streebog is the slowest hash in my use-case (https://disk-decipher.app), it determines the amount of time it takes to detect an incorrect password. So this improves UX and is therefor important to me. I noticed a recent NEON implementation here (https://github.com/vanyabeat/fast-streebog) though Botan outperforms that one by a mile (I added an issue in that repo about it). |
|
Forgot to mention: I usually build for Apple platforms, so mainly ARM64 these days.. |
|
What core are you using for your numbers? I tried your patch on two machines (Neoverse V3AE and a Cortex X925). On the Neoverse performance went from 145 Mb/s to 103 Mb/s, and on X925 from 226 Mb/s to 202 Mb/s |
|
#5655 adds an AVX-512 compression loop for Streebog, which is about 2.8-3x faster on my Tiger Lake laptop. I guess this doesn't help you much for an iOS app though. I tried a NEON implementation, couldn't find any approach that improved vs table lookups. |
Hmm, that's surprising. I tested on Apple M3 (my laptop), will do a test on my iPhone, that one has an older one. Maybe compiler difference, I used Apple Clang 21.0.0 with -O3 optimization. Which did you use? I could retry with g++ in a Linux VM on the same hardware if that's what you used, maybe g++ needs some extra help to reach the same level of optimization. |
|
This was with GCC (13 and 15). Unfortunately Clang install on both of those machines is broken so I can't easily test it without rebuilding LLVM. But in any case it seems like the results from this change are, at best, uneven. At the very least I'd want to more tightly restrict exactly the conditions this is used, for example limiting it to aarch64+clang+iOS. (Unless of course you're able to mod the patch in such a way that it improves for GCC as well) |
|
Fully agree, this can't be merged as-is. I'm setting up a Linux VM with g++ now on my laptop to see if I can reproduce the issue with g++. If so, I can try tweaking some optimization options. If that doesn't work out then restricting to aarch64+clang+apple (not just iOS, the app runs on macOS too) would be fine with me, though I'll first try to find the underlying cause, would be great if all ARM64 devices could benefit. |
|
Here are results, on the same Apple M3, with Debian GCC 14.2 in the VM, also using -O3 I'll retest inside this VM with clang, to be 100% sure it's the compiler. |
|
With Clang 19 So g++ definitely needs some help, let's see what compiler switches it has that might help. |
|
Tried a lot of different gcc optimization flags, none improved the benchmarks. I also tried the patch on my iPhone 15 Pro (has A17 Pro), that has same improvement as my notebook (both are compiled with clang). So this is consistently beneficial with Clang across two Apple SoCs and two Clang variants, while consistently harmful with GCC. I added "only if clang" to the #if. Let me know if you think this is worth merging, or too specific to my use-case (in which case I'll close this PR and keep the branch for my own usage). Both are fine with me. The failed ci/cd job was a timeout error unrelated to this PR, btw. |
Apply the LPS transform for the round key state and message state together inside Streebog::compress_64. These two transformations are independent and were previously executed back-to-back, so combining them exposes more table lookups to the compiler at once and improves instruction scheduling. This keeps the algorithm and output unchanged while improving Streebog throughput.
This improves Streebog throughput on ARM64 by applying two independent LPS transformations together inside
Streebog::compress_64.In each round,
AandhNare transformed back-to-back and are independent at that point. The new ARM64-only helper processes both states together, exposing more table lookups to the compiler and improving instruction scheduling. The algorithm and output are unchanged.The optimization is limited to
BOTAN_TARGET_ARCH_IS_ARM64. Testing on x86_64 showed that applying this unconditionally regressed performance, likely due to increased register pressure, so other architectures keep the existing two separatelpscalls.Benchmarks
Commands used:
Median throughput from 3 runs:
Testing
hash_algospassed, including the Streebog-256 and Streebog-512 known-answer tests.