Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simd sha256 intrinsics for x86 machines #1962

Merged
merged 1 commit into from Aug 17, 2023

Conversation

sanket1729
Copy link
Member

This is my first time dabbling into architecture specific code and simd. The algorithm is a word to word translation of the C code from https://github.com/noloader/SHA-Intrinsics/blob/4899efc81d1af159c1fd955936c673139f35aea9/sha256-x86.c .

Some benchmarks:

With simd

test sha256::benches::sha256_10                   ... bench:          11 ns/iter (+/- 0) = 909 MB/s
test sha256::benches::sha256_1k                   ... bench:         712 ns/iter (+/- 2) = 1438 MB/s
test sha256::benches::sha256_64k                  ... bench:      45,597 ns/iter (+/- 189) = 1437 MB/s

Without simd

test sha256::benches::sha256_10                   ... bench:          47 ns/iter (+/- 0) = 212 MB/s
test sha256::benches::sha256_1k                   ... bench:       4,243 ns/iter (+/- 17) = 241 MB/s
test sha256::benches::sha256_64k                  ... bench:     271,263 ns/iter (+/- 1,610) = 241 MB/s

@RCasatta
Copy link
Collaborator

Thanks for this!

I confirm I have similar benchmark improvements on my machine.

The error in CI is just formatting, a cargo +nightly fmt is needed.

Any rationale on choosing the code to base the implementation on over using the code in bitcoin core or in sha2 crate?

Is having some fuzzing between hardware and software implementation a good idea?

@apoelstra
Copy link
Member

@RCasatta the cited code is public domain. If we took from Bitcoin Core we'd have to ask the author to let us relicense under CC1.0 (which would probably be fine, I assume it's sipa or bluematt, but it's still extra work).

apoelstra
apoelstra previously approved these changes Jul 27, 2023
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 957d162

@sanket1729
Copy link
Member Author

Any rationale on choosing the code to base the implementation on over using the code in bitcoin core or in sha2 crate?

No strong reason. Bitcoin core code is also based on the same implementation. The sha256.cpp has the following note on top of it.

// Based on https://github.com/noloader/SHA-Intrinsics/blob/master/sha256-x86.c,
// Written and placed in public domain by Jeffrey Walton.
// Based on code from Intel, and by Sean Gulley for the miTLS project.

As for sha2 crate, I was not sure if the we would need permission to relicense it based on CC0. I am sure we can jut copy and they would be okay, with it. But this one just seemed simpler. I don't mind switching to another implementation if that makes things easier.

@sanket1729
Copy link
Member Author

Is having some fuzzing between hardware and software implementation a good idea?

As for fuzzing, I don't think it would be required, because this code either works or fails completely. There are no data dependant branches. Maybe, @apoelstra has thoughts here.

@apoelstra
Copy link
Member

I agree with that. I think in the past we've had fuzztests that check compatibility between our crate and the RustCrypto one, and it (unsurprisingly) never ever found anything.

@yancyribbens
Copy link
Contributor

I notice the C implementation doesn't create a new function call for each block, although I suppose creating 64 new stack frames probably doesn't impact performance.

@apoelstra
Copy link
Member

A private function that is only called once is pretty-much guaranteed to be inlined.

@sanket1729
Copy link
Member Author

I notice the C implementation doesn't create a new function call for each block, although I suppose creating 64 new stack frames probably doesn't impact performance.

Yeah, I didn't want to touch other parts of the code or change the architecture of the code by moving the loop into process_block. If it turns to have a performance impact we can move the loop inside the function in a separate PR. I would like keep this PR only related to substituting the equivalent logic to simd.

@TheBlueMatt
Copy link
Member

Optimizing just sha is nice, but there's also a lot to be gained interleaving multiple sha256 operations at least in merkle tree building - we are very often taking 2/4/8/etc neighboring blocks of 64 bytes and hashing each block at once. IIRC in Bitcoin Core pre-sha-ni it made a huge difference, though I'm not sure what the improvement was interleaving post-sha-ni. No reason to hold this PR up on that or anything, just noting it in case you're excited to do more :)

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 546c012

@sanket1729
Copy link
Member Author

we are very often taking 2/4/8/etc neighboring blocks of 64 bytes and hashing each block at once. IIRC in Bitcoin Core pre-sha-ni it made a huge difference

I did some superficial simd instruction counting for 2/4/8 blocks interleaving methods from bitcoin core.
Using Tranform2_sha256d from bitcoin core saves about 28 calls to _mm_sha256msg1_epu32 compared to calling sha256::hash repeatedly. Both of the still have the same number of calls to _mm_sha256rnds2_epu32.

Overall, one sha256d(3 transforms) computation done without transform2 takes about 192 _mm_sha256rnds2_epu32 and 84 _mm_sha256msg1_epu32.

Based on my superficial evaluation, there is still some performance benefit to be gained, but certainly not a significant one.

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be lying if I said I groked this code, can we have a test (possibly during fuzzing) that hashes with the software implementation and the intrinsics one and asserts they are the same? (Although I forget why we stopped fuzzing against the Rust Crypto's version and if the reason we did that counters my request.)

#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
#[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
unsafe fn process_block_simd_x86_intrinsics(&mut self) {
// Code translated and based on from
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Code translated and based on from
// Code translated from and based on

@sanket1729
Copy link
Member Author

that hashes with the software implementation and the intrinsics one and asserts they are the same?

My expectation is that all the fixed tests fail if the implementation was wrong. They are checked indirectly as the we get the same hash value in all fixed tests with and without simd.
My opinion on fuzzing #1962 (comment).

I wanted to add some tests for this. I don't know how to test this in CI. Add target=native and hope that the CI machine has instructions to test it?

@apoelstra
Copy link
Member

Although I forget why we stopped fuzzing against the Rust Crypto's version and if the reason we did that counters my request.

If you can get a test vector that passes and a test vector that fails such a test, you'd pretty-much need to break the hash function. I don't think there's any need for testing correctness, beyond the existing fixed test vectors.

@tcharding
Copy link
Member

tcharding commented Aug 2, 2023

Oh yes , my bad, I should have convinced myself of unit test coverage before posting. read the thread before reviewing.

@tcharding
Copy link
Member

What's the plan from here @sanket1729, are we going to do the other hashes from noloader code too (sha1, sha512)?

@sanket1729
Copy link
Member Author

@tcharding, can do. But I don't think those would be nearly as impactful as sha256 change. Other hash functions are just not used that frequently in bitcoin. sha256 is in used transaction hash, block hash, checksums etc.

I think our efforts are better spent improving the sha256 hash engine. I think that having functions for Transform2, Transform4 and Transform8 might be more useful.
Basically, we should be able to write something like:
https://github.com/bitcoin/bitcoin/blob/2fa60f0b683cefd7956273986dafe3bde00c98fd/src/crypto/sha256.cpp#L731

I don't plan on doing that anytime soon. So it's up for grabs if anyone is interested.

@apoelstra
Copy link
Member

Fully agreed that we only really need to put work into sha2, and any spare effort that people want to put into this should be focused on sha2.

(I wouldn't refuse work on other hash functions if somebody did it, of course, but if they're making a choice upfront, sha2 is all that really matters.)

@tcharding
Copy link
Member

Cool, cheers.

@TheBlueMatt
Copy link
Member

I did some superficial simd instruction counting for 2/4/8 blocks interleaving methods from bitcoin core.

So my point about performance here wasn't about the instruction count, but rather the data inverleaving allowing the CPU to better parallelize internally, which can allow for much higher throughput without fewer instructions.

@sanket1729
Copy link
Member Author

sanket1729 commented Aug 4, 2023 via email

@tcharding
Copy link
Member

What's the status of this one @apoelstra? Are you are hoping for another review?

@apoelstra
Copy link
Member

Oh, I don't think I noticed your ACK @tcharding. Can you re-ack with the commit ID?

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 546c012

@tcharding
Copy link
Member

I must have clicked "approve" before somehow, woops. All good now.

@apoelstra apoelstra merged commit af85528 into rust-bitcoin:master Aug 17, 2023
29 checks passed
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.

None yet

6 participants