Skip to content

Optimize Streebog compression scheduling on ARM64#5653

Open
huven wants to merge 1 commit into
randombit:masterfrom
huven:fast-streebog
Open

Optimize Streebog compression scheduling on ARM64#5653
huven wants to merge 1 commit into
randombit:masterfrom
huven:fast-streebog

Conversation

@huven

@huven huven commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This improves Streebog throughput on ARM64 by applying two independent LPS transformations together inside Streebog::compress_64.

In each round, A and hN are 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 separate lps calls.

Benchmarks

Commands used:

for i in 1 2 3 ; do
  botan speed --msec=3000 --buf-size=8192 Streebog-256 Streebog-512
  botan speed --msec=3000 --buf-size=65536 Streebog-256 Streebog-512
  botan speed --msec=3000 --buf-size=1048576 Streebog-256 Streebog-512
done

Median throughput from 3 runs:

ARM64

Buffer      Algorithm      Before       After        Change
8192        Streebog-256   221.9 MiB/s  290.9 MiB/s  +31.1%
8192        Streebog-512   222.0 MiB/s  295.2 MiB/s  +33.0%
65536       Streebog-256   227.3 MiB/s  295.6 MiB/s  +30.1%
65536       Streebog-512   226.0 MiB/s  295.9 MiB/s  +30.9%
1048576     Streebog-256   227.6 MiB/s  303.3 MiB/s  +33.3%
1048576     Streebog-512   227.1 MiB/s  303.7 MiB/s  +33.7%

Testing

python3 configure.py --with-build-dir=build/streebog-lps2-guarded \
  --build-targets=static,cli,tests --disable-shared-library

make -f build/streebog-lps2-guarded/Makefile -j8 libs cli tests
build/streebog-lps2-guarded/botan-test hash_algos

hash_algos passed, including the Streebog-256 and Streebog-512 known-answer tests.

@randombit

Copy link
Copy Markdown
Owner

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

@huven

huven commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

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.

@randombit

Copy link
Copy Markdown
Owner

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.

@huven

huven commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

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).

@huven

huven commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Forgot to mention: I usually build for Apple platforms, so mainly ARM64 these days..

@randombit

Copy link
Copy Markdown
Owner

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

@randombit

Copy link
Copy Markdown
Owner

#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.

@huven

huven commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

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

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.

@randombit

Copy link
Copy Markdown
Owner

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)

@huven

huven commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

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.

@huven

huven commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

Here are results, on the same Apple M3, with Debian GCC 14.2 in the VM, also using -O3

Buffer     Hash   Master   lps2     Change
8192       256    230.1    194.8    -15.3%
8192       512    228.8    200.1    -12.6%
65536      256    231.9    203.3    -12.3%
65536      512    233.8    204.2    -12.7%
1048576    256    226.1    204.5     -9.6%
1048576    512    226.6    204.7     -9.7%

I'll retest inside this VM with clang, to be 100% sure it's the compiler.

@huven

huven commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

With Clang 19

Buffer     Hash   Master   lps2     Change
8192       256    215.9    278.0    +28.7%
8192       512    218.7    276.4    +26.4%
65536      256    223.4    276.4    +23.7%
65536      512    224.9    281.9    +25.3%
1048576    256    225.2    286.1    +27.0%
1048576    512    219.8    284.8    +29.6%

So g++ definitely needs some help, let's see what compiler switches it has that might help.

@huven

huven commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants