feat(pullsync): delivery batching performance analysis#5540
Draft
janos wants to merge 1 commit into
Draft
Conversation
Member
Author
|
One comment regarding TCP (and UDP) Dynamics, a standard internet packet (MTU) is about 1500 bytes. A single 4 KB chunk already requires 3 TCP packets. Whether you flush chunks to the OS individually or in a batch, the OS TCP stack will still stream them out in 1500-byte packets at the maximum rate the network allows. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pullsync Batching Performance Analysis
This PR gives the overview of the performance testing and profiling conducted to evaluate the proposed batched chunk delivery (
DeliveryBatch) against the legacy single-message delivery (v2.8.1) in thepullsyncprotocol. It should not be merged, as it is meant to reproduce the results, but it may be used for potential improvements.The goal is to identify if the batching of delivery messages in the pullsync protocol can result to a speedup in overall deliveries.
To simulate real-world networking overhead accurately, a benchmark was written that spins up real
libp2pnodes locally, multiplexes streams, and transmits chunks between a client and server.This approach gives a convenient testing, benchmarking and profiling, but the pullsync performance can be measured on the real network as well.
There are two benchmarks:
Benchmark
There are to benchmarks, one for he currently released bee v2.8.1 pullsync implementation and one for the proposed batched implementation. Both of them with 25 and 120 chunks delivered. Since the max batch size can be 25 chunks because of the limitation of 128KB payload size of protobuf. There is a possiblity to raise that limit, but with a danger to expose some vulnerability in the network.
Run the libp2p benchmark for 100 iterations tracking memory allocations:
go test -v -run=NONE -bench=BenchmarkDeliveryLibP2P -benchtime=100x -benchmem ./pkg/pullsync/delivery_test.go ./pkg/pullsync/pullsync_test.goProfiling
Ran Go's built-in
pproftools against both theBatchedandv2.8.1branches of the benchmark independently, to identify any bottlenecks and/or improvements.Generate CPU and Memory profiles for the Batched implementation:
Generate CPU and Memory profiles for the legacy v2.8.1 implementation:
Analyze:
CPU Profile
Both profiles reveal an identical bottleneck. In both implementations,
runtime.pthread_cond_wait,runtime.usleep, andruntime.pthread_cond_signalconsume the majority of all execution samples. This indicates the CPU is blocking onlibp2pnetwork I/O synchronization than performing protobuf serialization or algorithmic work.Batched:
v2.8.1 (Single Message):
Memory Profile
Batched:
v2.8.1 (Single Message):
Both the batched and unbatched implementations perform nearly identically over
libp2p(~99.07 msvs~99.08 msforn=25and~475.34 msvs~475.29 msforn=120).The separated CPU profiles reveal why this is the case: the top CPU consumers in both implementations are OS-level thread blocking operations (
runtime.pthread_cond_wait,runtime.usleep,runtime.kevent). These account for over 60-80% of all CPU samples depending on the run. Because the program is almost entirely bottlenecked by waiting forlibp2pstream data to traverse network sockets, any optimizations gained by batching protobuf structures in memory are negligible compared to the network latency.The memory profile shows that chunk parsing and cryptographic hashing (
golang.org/x/crypto/sha3.(*state).Sumandpb.(*Delivery).Unmarshal) dominate memory allocations.Even if the delimitedReaderMaxSize is raised to 1MB, allowing larger batches, no performance gains are visible.
Conclusion
Introducing a breaking protocol change to batch protobuf messages yields no statistical CPU performance gain over the multiplexer latency, so the simpler, single-message streaming approach is preferred.
I encourage everyone interested to validate the findings.
Checklist
Description
Open API Spec Version Changes (if applicable)
Motivation and Context (Optional)
Related Issue (Optional)
Screenshots (if appropriate):
AI Disclosure