Skip to content

Reduce receive-timeout state allocations#3334

Draft
He-Pin wants to merge 1 commit into
apache:mainfrom
He-Pin:perf/1668-receive-timeout-state
Draft

Reduce receive-timeout state allocations#3334
He-Pin wants to merge 1 commit into
apache:mainfrom
He-Pin:perf/1668-receive-timeout-state

Conversation

@He-Pin

@He-Pin He-Pin commented Jul 12, 2026

Copy link
Copy Markdown
Member

Motivation

An actor with receive timeout enabled currently recreates immutable (Duration, Cancellable) tuples while cancelling and rescheduling the timeout for every influencing message. This puts two short-lived Tuple2 objects on the ActorCell.invoke hot path even though receive-timeout state is mailbox-thread confined.

Modification

  • Replace tuple replacement with one lazily allocated mutable receive-timeout state object.
  • Track state changes with an Int counter compared only across one message invocation, preserving the previous tuple-reference identity semantics without per-message state allocation.
  • Extend ReceiveTimeoutSpec with a 100-message reset burst and an explicit assertion that the timeout is measured from the final processed message.
  • Add an end-to-end Actor JMH benchmark that processes real mailbox messages while receive timeout is active and supports the JMH GC profiler.

Result

JDK 17.0.17, G1, one Actor/dispatcher thread, 10,000 messages per invocation, 3 forks, 5 x 1 second warmup and measurement:

Implementation Throughput Steady allocation
Immutable tuple state 16.049 ± 1.323 messages/µs 168.018 B/message
Reused mutable state 16.476 ± 1.825 messages/µs 120.018 B/message

The allocation result is stable across all non-teardown measurement iterations: exactly 48 B/message less, a 28.6% reduction in total allocation for this real Actor path. Throughput confidence intervals overlap, so this PR claims allocation/GC improvement rather than a statistically significant CPU improvement.

The state object is allocated only when an actor first configures receive timeout; actors that never enable it retain the existing null-reference footprint. Public Actor APIs and behavior are unchanged.

References

Refs #1668

Motivation:
Actors with receive timeout enabled recreated two Tuple2 state objects for every influencing message, adding avoidable allocation pressure to the ActorCell hot path.

Modification:
Replace immutable tuple snapshots with one lazily allocated mutable state object and a per-invocation change counter. Add a burst-reset regression test and an end-to-end Actor JMH benchmark with GC profiling.

Result:
On JDK 17 the real Actor benchmark reduces stable allocation from 168.018 to 120.018 B/message, exactly 48 B/message or 28.6%. Throughput remains neutral within the confidence interval.

Tests:
- actor-tests / Test / testOnly org.apache.pekko.actor.ReceiveTimeoutSpec (13 passed)
- bench-jmh / Jmh / compile
- ReceiveTimeoutBenchmark with JMH gc profiler on JDK 17.0.17
- +mimaReportBinaryIssues (Scala 2.13 and Scala 3 passed)
- +headerCheckAll and checkCodeStyle
- scalafmt diff check and git diff --check
- Qoder review: No must-fix findings
- validatePullRequest not completed locally per user request; rely on CI

References:
Refs apache#1668
@He-Pin He-Pin marked this pull request as draft July 12, 2026 04:05
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.

1 participant