Coalesce communication channel notifications - #815
Conversation
Preserve an immediate first-message wake while batching redundant notifications until the pusher's done boundary. Close input batches explicitly and cover same-epoch reactivation and inter-thread tail delivery.
|
Thanks for the PR! It prompted some investigation about the code path, and the conclusion was that this change introduces a consequential regression, but there is a similar change. #816 has it as one of the commits, but it is to notify on each data transmission, but not at the end of the feed when The reasoning is that in a concurrent system, it's important to signal when there is work to be done, for reasons of performance not only liveness. Signaling only at I had Claude do a mock up of this, and with one worker sending another eight messages, varying the time required to retire each of them, the time taken looks like so: #816 should also clean up a bunch of noise in the measurements, as busy-waiting for a few microseconds is an important clean-up: otherwise the various notifications stand in for busy waiting, and removing notifications slows things down only because the workers are more often found asleep, but not because they are awake at the right moments to do work. |
Problem & Solution Overview
Timely's communication counters currently enqueue a channel activation for every pushed message and for the final
done()call. The worker later sorts and deduplicates those channel IDs, so a batch ofNmessages createsN + 1notifications even though most collapse into one useful scheduling decision. This adds MPSC traffic, thread wake attempts, event-queue growth, and deduplication work under bursty or backpressured workloads.Timely previously had done-only coalescing, but it was disabled because some producers did not reliably close batches and because waiting until
done()delayed receiver pipelining. This change uses bounded coalescing instead: notify immediately for the first message, suppress intermediate notifications, and emit one trailing notification for a multi-message batch. Inputflush()andsend_batch()now establish explicit batch boundaries so same-epoch input remains responsive after a worker becomes idle.Summary
N + 1notifications to one notification for a single-message batch or at most two for a multi-message batch.InputHandle::flush()andsend_batch()close their batches;send_batch()closes exactly one combined buffered/direct batch.Pushimplementations may coalesce follow-up notifications untildone().Details
pingpongscenario.done()calls produce no notification, and single-message batches do not need a trailing wake.done()when dropped. The input changes supply the missing boundaries identified in issue Communication: coalesce counters #243.done()boundary can still defer later messages after the first wake. Eliminating that requirement would need a more invasive receiver-acknowledged atomic state spanning allocator event delivery.send_batch()restores one notification per direct batch--the same useful activation behavior that per-message notification previously provided.Performance Results
The example syntax is
exchange <batch> <rounds> -w <workers>:batchis the number of records inserted at each logical timestamp/epoch.roundsis the number of timestamps/epochs executed.-wis the number of Timely worker threads.For example,
exchange 1 200000 -w 8runs 200,000 one-record epochs across eight workers. This intentionally emphasizes per-batch notification and scheduling overhead rather than record-processing throughput.Measurements used release builds on local macOS and alternated clean
masterand patched executions to reduce run-order bias.Notification hot-path microbenchmark
This synthetic benchmark wraps a no-op data pusher with the inter-thread counter and drains its notification channel on another thread. It measures only sender-side notification-channel sends and wake attempts; it does not execute Timely operators, progress tracking, exchange routing, serialization, or record processing.
masterThis result establishes the direct mechanism-level saving. It is not an expected whole-dataflow speedup.
End-to-end: controlled worker scaling
The batch size and epoch count are fixed at
exchange 1 200000; only the worker count changes. Each row reports the median of ten runs per revision.mastermedianThe near-neutral one-worker result is expected: it uses intra-thread delivery and avoids the MPSC sends and cross-thread wakeups targeted by the largest part of this change. The benefit grows with worker count as those costs become a larger share of each one-record epoch.
End-to-end: larger record batch
This row reports the median of eight runs per revision and demonstrates that the change remains beneficial when useful record-processing work dominates notification overhead.
mastermedianexchange 100000 5000 -w 2Steady-state
pingpongremained neutral. The intended benefit is lower scheduling overhead and bounded notification backlog for frequent batch boundaries, bursty senders, and lagging receivers--not a claim that every Timely workload becomes faster.Result scope summary
exchange 1 200000, 1-8 workersexchange 100000 5000 -w 2pingpongTesting Done
This change added tests and was verified as follows:
Testing Done
send_batch()and explicitflush()reactivation coveredValidation completed:
cargo test --workspace --quiet: 265 passed, 48 ignored, 0 failedtimelyandtimely_communicationwith warnings denied after allowing only lint classes already present on cleanmasterunder the current Rust toolchaincargo test -p timely_communication --test counterscargo test -p timely --test coalesced_inputgit diff --checkexchangebenchmark