8000 Add ASM optimizations for MuHash3072 by fjahr · Pull Request #19181 · bitcoin/bitcoin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add ASM optimizations for MuHash3072 #19181

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

Closed
wants to merge 1 commit into from

Conversation

fjahr
Copy link
Contributor
@fjahr fjahr commented Jun 5, 2020

Adds assembly optimizations for MuHash3072 which is used for the muhash calculation of the UTXO set hash.

@DrahtBot
Copy link
Contributor
DrahtBot commented Jun 7, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK Sjors

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

No conflicts as of last run.

@Sjors
Copy link
Member
Sjors commented Jun 9, 2020

I'll post some bench results here after #19214 and when the SHA512 -> 256 switch is implemented.

@laanwj
Copy link
Member
laanwj commented Jun 9, 2020

Awesome, thanks for working on this. Given how good compilers are nowadays it's somewhat surprising to me that there's still something to gain with implementing things manually in assembly without use of any special instruction sets.

@Sjors
Copy link
Member
Sjors commented Dec 15, 2021

Concept ACK. Description needs an update. And this still needs a rebase.

@fjahr
Copy link
Contributor Author
fjahr commented Dec 19, 2021

Thanks for the nudge @Sjors and @DrahtBot !

I have only done a plain rebase so far, i.e. I have not checked whether further optimizations may be interesting to look into. But while I ran the benchmarks again I noticed that it currently appears that this has slower benchmarks than master on my machine. mul and div operations appear to be almost 20% slower. I will try to test on some other machines soon but if someone else can give it a spin as well it would be a great help!

Results with this PR

$ src/bench/bench_bitcoin -filter=MuHash.* -min_time=1000

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            7,184.06 |          139,197.00 |    0.8% |      1.07 | `MuHash`
|            6,138.95 |          162,894.31 |    0.1% |      1.05 | `MuHashDiv`
|            6,161.80 |          162,290.16 |    0.7% |      1.05 | `MuHashMul`
|            1,016.96 |          983,326.67 |    0.3% |      1.08 | `MuHashPrecompute`

Results on master

$ src/bench/bench_bitcoin -filter=MuHash.* -min_time=1000

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            6,225.79 |          160,622.20 |    1.6% |      1.05 | `MuHash`
|            5,121.86 |          195,241.46 |    0.5% |      1.09 | `MuHashDiv`
|            5,173.32 |          193,299.36 |    1.5% |      1.11 | `MuHashMul`
|            1,023.41 |          977,127.42 |    0.7% |      1.08 | `MuHashPrecompute`

@maflcko
Copy link
Member
maflcko commented Dec 20, 2021

With gcc 11.2.0 on Cortex-A72.

This:

src/bench/bench_bitcoin -filter=MuHash.* 
Warning, results might be unstable:
* CPU frequency scaling enabled: CPU 0 between 600.0 and 1,500.0 MHz
* CPU governor is 'ondemand' but should be 'performance'
* Turbo is enabled, CPU frequency will fluctuate

Recommendations
* Use 'pyperf system tune' before benchmarking. See https://github.com/psf/pyperf

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|           27,412.56 |           36,479.63 |    0.1% |      0.01 | `MuHash`
|           24,505.61 |           40,806.99 |    0.0% |      0.01 | `MuHashDiv`
|           24,513.39 |           40,794.02 |    0.0% |      0.01 | `MuHashMul`
|            2,874.73 |          347,859.09 |    0.0% |      0.01 | `MuHashPrecompute`

Master:

src/bench/bench_bitcoin -filter=MuHash.* 
Warning, results might be unstable:
* CPU frequency scaling enabled: CPU 0 between 600.0 and 1,500.0 MHz
* CPU governor is 'ondemand' but should be 'performance'
* Turbo is enabled, CPU frequency will fluctuate

Recommendations
* Use 'pyperf system tune' before benchmarking. See https://github.com/psf/pyperf

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|           27,451.77 |           36,427.52 |    0.0% |      0.01 | `MuHash`
|           24,524.13 |           40,776.16 |    0.1% |      0.01 | `MuHashDiv`
|           24,494.58 |           40,825.36 |    0.0% |      0.01 | `MuHashMul`
|            2,853.94 |          350,392.61 |    0.0% |      0.01 | `MuHashPrecompute`

@sipa
Copy link
Member
sipa commented Dec 20, 2021

@MarcoFalke Well, that's expected at least. There are only x86_64 asm implementations here.

@fjahr What kind of system/compiler/flags?

@sipa
Copy link
Member
sipa commented Dec 20, 2021

Benchmark on Ryzen 5950X, Ubuntu 21.10, GCC 11.2.0, default configure flags.

master:

ns/op op/s err% total benchmark
4,133.94 241,900.24 0.5% 0.01 MuHash
3,530.11 283,277.63 0.4% 0.01 MuHashDiv
3,522.44 283,894.43 0.2% 0.01 MuHashMul
554.91 1,802,107.77 0.1% 0.01 MuHashPrecompute

This PR:

ns/op op/s err% total benchmark
3,157.98 316,658.14 0.3% 0.01 MuHash
2,597.48 384,987.98 0.1% 0.01 MuHashDiv
2,597.10 385,044.86 0.2% 0.01 MuHashMul
556.71 1,796,271.37 0.0% 0.01 MuHashPrecompute

@maflcko
Copy link
Member
maflcko commented Dec 21, 2021

@MarcoFalke Well, that's expected at least. There are only x86_64 asm implementations here.

I wish I could read asm. 😅

@fjahr
Copy link
Contributor Author
fjahr commented Dec 21, 2021

@fjahr What kind of system/compiler/flags?

Hmm, this was with Intel Core i5-6287U, darwin 21.2.0 (macOS 12.1), clang 13.0.0, default configure flags except for skipping the GUI.

@sipa
Copy link
Member
sipa commented Dec 21, 2021

@fjahr Did you disable frequency scaling? Mobile CPUs by default will change frequency all the time in response to load, leading to unreliable benchmarks.

Copy link
Contributor
@theStack theStack left a comment

Choose a reason for hiding this comment

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

Results using gcc 11.3.0 and default configuration on a AMD EPYC 7702P (running Ubuntu 22.04):

master:

ns/op op/s err% total benchmark
6,810.64 146,829.16 2.5% 0.01 MuHash
5,947.59 168,135.40 1.0% 0.01 MuHashDiv
5,756.02 173,731.09 0.8% 0.01 MuHashMul
838.00 1,193,321.69 0.4% 0.01 MuHashPrecompute

PR:

ns/op op/s err% total benchmark
5,457.87 183,221.54 0.4% 0.01 MuHash
4,605.19 217,146.38 0.9% 0.01 MuHashDiv
4,615.33 216,669.07 0.7% 0.01 MuHashMul
848.51 1,178,540.85 0.7% 0.01 MuHashPrecompute

For a more practical test, I ran gettxoutset muhash without coinstatsindex on mainnet height 765184 both on master and the PR (started with -nolisten -noconnect to avoid distractions for the benchmark), showing a nice ~18% speedup:

master:

$ time ./src/bitcoin-cli gettxoutsetinfo muhash
{
  "height": 765184,
  "bestblock": "00000000000000000006923ee26b9b3d271035b5cdf79f4915d8453cb3a6f305",
  "txouts": 83195150,
  "bogosize": 6203273828,
  "muhash": "b645663cd8a7a4b6083a84940199f17125232ab4b126602ed2aa054844503393",
  "total_amount": 19219692.16624067,
  "transactions": 49805302,
  "disk_size": 5992277808
}

real    6m28.066s
user    0m0.003s
sys     0m0.001s

PR:

$ time ./src/bitcoin-cli gettxoutsetinfo muhash


{
  "height": 765184,
  "bestblock": "00000000000000000006923ee26b9b3d271035b5cdf79f4915d8453cb3a6f305",
  "txouts": 83195150,
  "bogosize": 6203273828,
  "muhash": "b645663cd8a7a4b6083a84940199f17125232ab4b126602ed2aa054844503393",
  "total_amount": 19219692.16624067,
  "transactions": 49805302,
  "disk_size": 5991699018
}

real    5m28.506s
user    0m0.003s
sys     0m0.000s

I seem to get consistently better results than master on GCC but also consistently worst results on clang.

Interesting, will als repeat the above test runs using clang in a bit to see if I can observe the same effect.

Copy link
Contributor
@theStack theStack left a comment

Choose a reason for hiding this comment

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

I seem to get consistently better results than master on GCC but also consistently worst results on clang.

I can confirm that the MuHash performance with this PR is worse than on master when compiling using clang 14.0.0 (default configuration on a AMD EPYC 7702P, running Ubuntu 22.04). Benchmark results:

master:

ns/op op/s err% total benchmark
5,476.23 182,607.35 1.8% 0.01 MuHash
4,570.74 218,782.88 1.0% 0.01 MuHashDiv
4,547.88 219,882.44 0.9% 0.01 MuHashMul
814.77 1,227,343.46 0.8% 0.01 MuHashPrecompute

PR:

ns/op op/s err% total benchmark
5,799.24 172,436.30 0.6% 0.01 MuHash
5,022.26 199,113.39 1.3% 0.01 MuHashDiv
4,954.25 201,847.07 0.3% 0.01 MuHashMul
828.04 1,207,671.75 0.8% 0.01 MuHashPrecompute

And the gettxoutsetinfo muhash tests for mainnet block 765184:

master:

$ time ./src/bitcoin-cli gettxoutsetinfo muhash
{
  "height": 765184,
  "bestblock": "00000000000000000006923ee26b9b3d271035b5cdf79f4915d8453cb3a6f305",
  "txouts": 83195150,
 
8000
 "bogosize": 6203273828,
  "muhash": "b645663cd8a7a4b6083a84940199f17125232ab4b126602ed2aa054844503393",
  "total_amount": 19219692.16624067,
  "transactions": 49805302,
  "disk_size": 5991686963
}

real    5m36.934s
user    0m0.002s
sys     0m0.002s

PR:

$ time ./src/bitcoin-cli gettxoutsetinfo muhash
{
  "height": 765184,
  "bestblock": "00000000000000000006923ee26b9b3d271035b5cdf79f4915d8453cb3a6f305",
  "txouts": 83195150,
  "bogosize": 6203273828,
  "muhash": "b645663cd8a7a4b6083a84940199f17125232ab4b126602ed2aa054844503393",
  "total_amount": 19219692.16624067,
  "transactions": 49805302,
  "disk_size": 5991686963
}

real    5m52.723s
user    0m0.004s
sys     0m0.000s

(Sorry for the long double-posts, I should summarize both on them on a small summary table)

Based on those results, if at all, those optimizations should probably only be enabled if GCC is used? (Not sure if it's worth the review and maintenance burden though.)

@achow101
Copy link
Member

@real-or-random

@real-or-random
Copy link
Contributor

Based on those results, if at all, those optimizations should probably only be enabled if GCC is used? (Not sure if it's worth the review and maintenance burden though.)

Some of the code changes here conflict with #21590, and given that clang 14 is better than this ASM, we should maybe get benchmarks on a recent GCC (like 12.2) and see if it's faster. If not, I also wonder if there's a better way to convince GCC to output good code.

@theStack Can you re-benchmark with GCC 12 ? I can also try to get some numbers on my machine.

@maflcko
Copy link
Member
maflcko commented Apr 26, 2023

Maybe even with gcc 13.1, now that it is about to be released?

@fjahr
Copy link
Contributor Author
fjahr commented May 1, 2023

I finally got another amd64 machine with which I can test again. I confirmed the clang-14 results others have seen and I tested with GCC 13.1. The results still look good there for me but would be great if someone else could confirm.

(using src/bench/bench_bitcoin -filter=MuHash.* -min-time=1000)

Master:

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            9,000.29 |          111,107.54 |    1.6% |      1.09 | `MuHash`
|            7,424.35 |          134,691.90 |    0.0% |      1.05 | `MuHashDiv`
|            7,428.86 |          134,610.13 |    0.2% |      1.06 | `MuHashMul`
|            1,058.97 |          944,314.00 |    0.0% |      1.08 | `MuHashPrecompute`

This PR:

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            6,936.64 |          144,161.92 |    0.1% |      1.08 | `MuHash`
|            5,596.30 |          178,689.37 |    0.2% |      1.03 | `MuHashDiv`
|            5,422.52 |          184,415.94 |    0.7% |      1.08 | `MuHashMul`
|            1,061.63 |          941,950.39 |    0.0% |      1.09 | `MuHashPrecompute`

I guess it makes sense to only use this when GCC is used which should work with this

#if (defined(__amd64__) || defined(__x86_64__)) && defined(__GNUC__) && !defined(__clang__)

but I would also be curious if there is another way to tell GCC to do the same optimizations clang seems to be doing.

I will also try to combine the changes here with #21590 to see what the combined impact is.

@real-or-random
Copy link
Contributor

#if (defined(__amd64__) || defined(__x86_64__)) && defined(__GNUC__) && !defined(__clang__)

If it's that niche, it's a bit unclear to me whether it's worth the hassle. I feel we should look at #21590 first. I expect this to be a much larger improvement (and since it's algorithmic, it will apply to all targets). Perhaps we don't care about this optimization here so much after #21590.

@fjahr
Copy link
Contributor Author
fjahr commented May 2, 2023

I have now tested SafeGCD vs SafeGCD+ASM (see https://github.com/fjahr/bitcoin/tree/pr21590-safegcd-asm) and the gains from including the ASM code are still substantial.

GCC 13.1 - SafeGCD only:

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            9,168.73 |          109,066.35 |    2.1% |      1.06 | `MuHash`
|            7,571.75 |          132,069.88 |    0.2% |      1.05 | `MuHashDiv`
|           75,079.98 |           13,319.13 |    0.0% |      1.06 | `MuHashFinalize`

9E88
|            7,322.87 |          136,558.39 |    0.1% |      1.05 | `MuHashMul`
|            1,052.74 |          949,904.92 |    0.0% |      1.09 | `MuHashPrecompute`

GCC 13.1 - SafeGCD + ASM:

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|            6,924.98 |          144,404.76 |    0.2% |      1.08 | `MuHash`
|            5,631.55 |          177,571.08 |    0.1% |      1.09 | `MuHashDiv`
|           70,266.78 |           14,231.48 |    0.8% |      1.05 | `MuHashFinalize`
|            5,454.11 |          183,347.95 |    0.1% |      1.09 | `MuHashMul`
|            1,051.74 |          950,801.50 |    0.0% |      1.09 | `MuHashPrecompute`

@real-or-random while this change here is niche in terms of the tooling+architecture it targets, the SafeGCD change only impacts the finalize operation and that has a much smaller impact on the overall computation time because of how we use MuHash in practice. When a new block comes in we do a div of all the spent TXOs and a mul of all the new UTXOs and then at the end we finalize once. So I still have a hard time deciding which one is the more valuable change. Also, while ASM might be a bit intimidating, #21590 has 10x the LOC changed and requires some understanding of the underlying theory.

@real-or-random
Copy link
Contributor

the SafeGCD change only impacts the finalize operation and that has a much smaller impact on the overall computation time because of how we use MuHash in practice. When a new block comes in we do a div of all the spent TXOs and a mul of all the new UTXOs and then at the end we finalize once.

Okay, thanks for the numbers. I agree then, we shouldn't neglect this one.

So I still have a hard time deciding which one is the more valuable change. Also, while ASM might be a bit intimidating, #21590 has 10x the LOC changed and requires some understanding of the underlying theory.

Yeah, they both have their merits then, and I don't think any should be prioritized over the other. Let's do (first) whatever PR we get the ACKs on. (And yes, I expect #21590 to be harder to review...)

but I would also be curious if there is another way to tell GCC to do the same optimizations clang seems to be doing.

That would be ideal. I'll try to have a look at this.

@real-or-random
Copy link
Contributor
real-or-random commented May 2, 2023

but I would also be curious if there is another way to tell GCC to do the same optimizations clang seems to be doing.

I played around with this a bit, and I don't see any obvious trick to make that work. If someone else wants to give it a try, https://gcc.godbolt.org/z/hhGfeEoKq could be a nice starting point.

@DrahtBot
Copy link
Contributor
DrahtBot commented Aug 8, 2023

There hasn't been much activity lately. What is the status here?

Finding reviewers may take time. However, if the patch is no longer relevant, please close this pull request. If the author lost interest or time to work on this, please close it and mark it 'Up for grabs' with the label, so that it can be picked up in the future.

@fjahr
Copy link
Contributor Author
fjahr commented Aug 8, 2023

There hasn't been much activity lately. What is the status here?

Finding reviewers may take time. However, if the patch is no longer relevant, please close this pull request. If the author lost interest or time to work on this, please close it and mark it 'Up for grabs' with the label, so that it can be picked up in the future.

Still relevant... How good is your ASM, @DrahtBot ? 😁

@achow101 achow101 requested review from theStack and sipa September 20, 2023 17:31
@sipa
Copy link
Member
sipa commented Sep 29, 2023

It's a rather unsatisfying situation that a compiler produces better code than hand-written assembly. One possibility is just taking the asm generated by clang 14 and including that as asm blocks in the C++ code?

@real-or-random
Copy link
Contributor

It's a rather unsatisfying situation that a compiler produces better code than hand-written assembly. One possibility is just taking the asm generated by clang 14 and including that as asm blocks in the C++ code?

Yes, but what would be a proper way of reviewing such a PR? Just comparing with the clang output? If we think that's sufficient, then that's a possible way forward.

@real-or-random
Copy link
Contributor
real-or-random commented Nov 8, 2023

There's a lot of activity recently in GCC towards generating adc instructions on x86(_64): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79173 But the current GCC trunk (14) so far hasn't improved over GCC 13 for over code. The GCC trhead also has some hints at possible other implementations such as GCC builtins, which may be simpler to review.

I don't know. Checking the asm annotations is not trivial and not many people are familiar with inline asm. But on the other hand, I'm not saying that this PR is a huge review effort. It's just a handful of small functions, and someone needs to take the time.

@achow101
Copy link
Member
achow101 commented Apr 9, 2024

One possibility is just taking the asm generated by clang 14 and including that as asm blocks in the C++ code?

Seems like we should do that.

@achow101
Copy link
Member
achow101 commented Apr 9, 2024

Closing as up for grabs due to lack of activity.

@laanwj
Copy link
Member
laanwj commented Apr 9, 2024

One possibility is just taking the asm generated by clang 14 and including that as asm blocks in the C++ code?

In general i really dislike the idea of copy/pasting assembly output from a compiler into the source code. It's already hard enough to review human-generated asm code but at least you can ask the author about the reasoning how and why. In the case of compiler output, well, it'd be a matter of waiting for gcc to improve 🙂

@maflcko
Copy link
Member
maflcko commented Apr 9, 2024

Removing "up for grabs" for now. I don't think anyone will review asm, regardless of where it came from? If there are reviewers who would review it, they should speak up first, no?

@laanwj
Copy link
Member
laanwj commented Apr 9, 2024

To be clear, I'm happy to review asm if there's 1) a very clear performance win in an important part of the code 2) it's human-written and well commented 3) it's only small and relatively straightforward, self-contained operations.
With how good compilers are nowadays it should be rare, though. With new instruction sets it's generally better (also for review-easier to check data flow) to use intrinsics instead of direct inline assembly.

@bitcoin bitcoin locked and limited conversation to collaborators Apr 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0