Skip to content

Conversation

@MarcoPolo
Copy link
Contributor

Builds on #582. 10x faster than current master. 0 allocs.

The basic logic is the same as the old version, except we return an iter.Seq[RPC] and yield RPC types instead of a slice of *RPC. This lets us avoid allocations for heap pointers.

Please review @algorandskiy, and let me know if this improves your use case.

gossipsub.go Outdated
// split splits the given RPC If a sub RPC is too large and can't be split
// further (e.g. Message data is bigger than the RPC limit), then it will be
// returned as an oversized RPC. The caller should filter out oversized RPCs.
func (rpc *RPC) split(limit int) iter.Seq[RPC] {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method should likely be in the pubsub.go file instead. I kept it here to make reviewing easier, but would like to move it after reviewer's approval.

@MarcoPolo
Copy link
Contributor Author

benchstat:

goos: darwin
goarch: arm64
pkg: github.com/libp2p/go-libp2p-pubsub
cpu: Apple M1 Max
                                             │   old.txt    │               new.txt               │
                                             │    sec/op    │   sec/op     vs base                │
SplitRPCLargeMessages/Many_large_messages-10   51.751µ ± 0%   4.327µ ± 1%  -91.64% (p=0.000 n=10)
SplitRPCLargeMessages/2_large_messages-10      320.70n ± 2%   34.63n ± 0%  -89.20% (p=0.000 n=10)
geomean                                         4.074µ        387.0n       -90.50%

                                             │   old.txt    │                 new.txt                  │
                                             │     B/op     │    B/op      vs base                     │
SplitRPCLargeMessages/Many_large_messages-10   18.65Ki ± 0%   0.00Ki ± 0%  -100.00% (p=0.000 n=10)
SplitRPCLargeMessages/2_large_messages-10        392.0 ± 0%      0.0 ± 0%  -100.00% (p=0.000 n=10)
geomean                                        2.672Ki                     ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean

                                             │  old.txt   │                 new.txt                 │
                                             │ allocs/op  │ allocs/op   vs base                     │
SplitRPCLargeMessages/Many_large_messages-10   217.0 ± 0%     0.0 ± 0%  -100.00% (p=0.000 n=10)
SplitRPCLargeMessages/2_large_messages-10      8.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
geomean                                        41.67                    ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean

@MarcoPolo MarcoPolo force-pushed the marco/rpc_split_2 branch from f4afabe to 3fb6361 Compare May 27, 2025 16:07
Copy link
Contributor

@algorandskiy algorandskiy left a comment

Choose a reason for hiding this comment

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

Looks great and impressive +90% perf gain comparing to mine feeb +66% :)

gossipsub.go Outdated
lastRPC.Subscriptions = append(lastRPC.Subscriptions, sub)
out = append(out, lastRPC)
for _, sub := range rpc.Subscriptions {
if nextRPC.Subscriptions = append(nextRPC.Subscriptions, sub); nextRPC.Size() > limit {
Copy link
Contributor

Choose a reason for hiding this comment

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

nextRPC.Size() - this is the same issue with calling Size in a loop over and over again as for Publish but probably does not matter much as (from my understanding) new subscriptions as well as control are relatively rare compare to Publish thing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly. Notice that we flush the messages to publish above. So here we aren't calling .Size on Publish messages again.

Splitting due to control messages should be extremely rare (happy to be proven wrong), so I opted to do the easier thing and keep the old code.

Thinking about this just now, we could do a fast path check before these loops that check if the original RPC without the publish messages is small enough to fit and can be yielded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this, and it adds a nice small improvement. Many large benchmark is around 3.3 us, and 2 large is around 28 ns.

@MarcoPolo MarcoPolo merged commit c405ca8 into master May 29, 2025
7 checks passed
@MarcoPolo MarcoPolo deleted the marco/rpc_split_2 branch May 29, 2025 05:32
@MarcoPolo MarcoPolo mentioned this pull request May 29, 2025
MarcoPolo added a commit that referenced this pull request May 29, 2025
This release contains a couple fixes and the new Batch Publishing
feature.

- #607 Batch Publishing. Useful if you are publishing a group of related
messages at once
- #612 Send IDONTWANT before initial publish. Useful when many nodes may
publish the same message at once.
- #609 Avoid sending an extra "IDONTWANT" to the peer that just sent you
a message.
- #615 10x faster rpc splitting.
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.

3 participants