8000 utxocache: replace mapslice with concurrent-swiss-map by rabbitprincess · Pull Request #2346 · btcsuite/btcd · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

utxocache: replace mapslice with concurrent-swiss-map #2346

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rabbitprincess
Copy link
Contributor

Change Description

Currently, utxocache uses mapslice instead of a single large map. This seems to be necessary because Go's built-in map can consume a significant amount of memory when scaled.
However, I suggest using ConcurrentSwissMap as an alternative, which offers simple implementation and better performance in concurrent reads.

Benchmark

Environment
GOOS: linux
GOARCH: amd64
CPU: AMD EPYC 7571
PKG: github.com/btcsuite/btcd/blockchain

Type Put Get Delete
MapSlice 209.5 ns/op 153.8 ns/op 99.15 ns/op
Csmap 197.7 ns/op 252.5 ns/op 249.4 ns/op
MapSlice (8 Core) 539.7 ns/op 428.0 ns/op 287.6 ns/op
Csmap (8 Core) 430.9 ns/op 70.30 ns/op 482.8 ns/op

Csmap outperforms the lock-based mapslice by over 6x in concurrent reads.
You can verify the performance using the mapslice and concurrentSwissMap benchmarks.

Steps to Test

Steps for reviewers to follow to test the change.

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

@coveralls
Copy link

Pull Request Test Coverage Report for Build 14052973796

Details

  • 32 of 35 (91.43%) changed or added relevant lines in 1 file are covered.
  • 40 unchanged lines in 5 files lost coverage.
  • Overall coverage increased (+0.07%) to 56.648%

Changes Missing Coverage Covered Lines Changed/Added Lines %
blockchain/utxocache.go 32 35 91.43%
Files with Coverage Reduction New Missed Lines %
blockchain/utxocache.go 1 87.81%
btcutil/gcs/gcs.go 2 81.25%
blockchain/sizehelper.go 4 91.95%
wire/msgaddrv2.go 13 56.9%
wire/netaddressv2.go 20 74.45%
Totals Coverage Status
Change from base Build 14045691534: 0.07%
Covered Lines: 31032
Relevant Lines: 54780

💛 - Coveralls

@guggero
Copy link
Collaborator
guggero commented Mar 25, 2025

Mostly curious: How does the concurrent-swiss-map in the library you used differ from the native Swiss Tables that are now implemented in Go 1.24?

@saubyk saubyk requested a review from kcalvinalvin March 25, 2025 17:07
@Roasbeef
Copy link
Member

Csmap outperforms the lock-based mapslice by over 6x in concurrent reads.

What about allocs/op?

m := ms.makeNewMap(totalEntryMemory)
m[op] = entry
func (ms *csMap) size() int {
return calculateRoughMapSize(ms.length(), bucketSize)
Copy link
Member

Choose a reason for hiding this comment

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

I think we also need to modify calculateRoughMapSize, as it makes assumptions w.r.t the current runtime implementation. With the introduction of Swiss maps in Go 1.24, this is likely out of date anyway (results in incorrect estimates).

Copy link
Collaborator

Choose a reason for hiding this comment

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

This right?

https://go.dev/blog/swisstable

Maybe the map calculation might be easier now based on how the blog described it. We'll see...

@rabbitprincess
Copy link
Contributor Author
rabbitprincess commented Mar 26, 2025

Mostly curious: How does the concurrent-swiss-map in the library you used differ from the native Swiss Tables that are now implemented in Go 1.24?

It is basically same because both project based by dolthub-swissmap.
However, concurrent-swiss-map supports concurrency by sharding the map, with each shard having its own lock. This allows fast parallel reads as they often avoid locks entirely.

https://www.reddit.com/r/golang/comments/15jl2x7/a_highperformance_threadsafe_generic_concurrent/

@kcalvinalvin
Copy link
Collaborator

Concurrent reads/writes are not much of a problem for performance since the block validation
doesn't really have concurrent reads/writes. You start having that once you're synced to the tip
and need to fetch utxos for tx validation.

I think getting rid of mapslice is good since it's ugly. But I don't think the external dep makes
sense for consensus critical code.

Since the builtin map has multiple swiss tables, maybe we can just replace the entire mapslice
with just a single map and update the map size calculation code?

@rabbitprincess
Copy link
Contributor Author
rabbitprincess commented Mar 26, 2025

@kcalvinalvin To use swissmap, it need to update go.mod version to 1.24. Also, using concurrentSwissMap is a better option than default swissmap since it avoids global locks.

If needed, I can also include benchmarks for the 1.24 builtin swissmap.

@kcalvinalvin
Copy link
Collaborator

@kcalvinalvin To use swissmap, it need to update go.mod version to 1.24.

@Roasbeef?

Also, using concurrentSwissMap is a better option than default swissmap since it avoids global locks.

Fair point but I think avoiding an external dependency is better than avoiding global locks.

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.

5 participants
0