Skip to content

Fuse restInHandler into GraphStageLogic in Intersperse#2981

Open
He-Pin wants to merge 3 commits into
apache:mainfrom
He-Pin:optimize-split-handler-fusion
Open

Fuse restInHandler into GraphStageLogic in Intersperse#2981
He-Pin wants to merge 3 commits into
apache:mainfrom
He-Pin:optimize-split-handler-fusion

Conversation

@He-Pin
Copy link
Copy Markdown
Member

@He-Pin He-Pin commented May 20, 2026

Motivation

Optimize Intersperse stage by fusing the hot-path restInHandler into GraphStageLogic, reducing per-materialization object allocation.

Modification

Before:

new GraphStageLogic(shape) with OutHandler {
  val startInHandler = new InHandler { ... }  // cold path
  val restInHandler = new InHandler { ... }   // hot path - separate object
  setHandler(in, startInHandler)
  setHandler(out, this)
}

After:

new GraphStageLogic(shape) with InHandler with OutHandler { self =>
  val startInHandler = new InHandler { ... }  // cold path - still separate
  // hot path - fused into GraphStageLogic
  override def onPush(): Unit = ...
  override def onUpstreamFinish(): Unit = ...
  setHandler(in, startInHandler)
  setHandler(out, this)
}

Result

  • Eliminates one InHandler allocation per materialization on the hot path
  • No boolean checks added to hot path
  • Cold-path startInHandler remains as separate object (only used once)

Tests

  • stream-tests/testOnly FlowIntersperseSpec: 8/8 passed

He-Pin added 2 commits May 20, 2026 18:31
Optimize several stream stages by fusing separate InHandler/OutHandler
objects into the GraphStageLogic itself, reducing per-materialization
memory allocations:

1. DoOnFirst: Replace separate InHandler with boolean flag pattern
2. Intersperse: Merge two InHandler objects (start/rest) into single fused handler
3. Reduce: Eliminate initial InHandler by using hasFirst flag
4. DropWithin: Replace post-timeout InHandler with timedOut flag
5. Scan: Remove initial InHandler/OutHandler pair using initialized flag
6. ScanAsync: Remove ZeroHandler and post-completion OutHandler

Tests:
- stream-tests/testOnly FlowDoOnFirstSpec: 2/2 passed
- stream-tests/testOnly FlowIntersperseSpec: 8/8 passed
- stream-tests/testOnly FlowReduceSpec: 12/12 passed
- stream-tests/testOnly FlowDropWithinSpec: 2/2 passed
- stream-tests/testOnly FlowScanSpec: 8/8 passed
- stream-tests/testOnly FlowScanAsyncSpec: 19/19 passed
@He-Pin He-Pin marked this pull request as draft May 20, 2026 10:37
Optimize Intersperse stage by fusing the hot-path restInHandler into
GraphStageLogic itself, reducing per-materialization object allocation.

Before: GraphStageLogic with OutHandler + startInHandler + restInHandler
After:  GraphStageLogic with InHandler with OutHandler + startInHandler

The cold-path startInHandler (only used once for first element) remains
as a separate object. The hot-path restInHandler logic is now directly
in the fused GraphStageLogic, eliminating one InHandler allocation per
materialization without adding any boolean checks on the hot path.

Tests:
- stream-tests/testOnly FlowIntersperseSpec: 8/8 passed
@He-Pin He-Pin changed the title Fuse InHandler/OutHandler into GraphStageLogic to reduce allocations Fuse restInHandler into GraphStageLogic in Intersperse May 20, 2026
@He-Pin He-Pin marked this pull request as ready for review May 20, 2026 11:06
@He-Pin He-Pin marked this pull request as draft May 20, 2026 12:30
@He-Pin He-Pin requested review from Copilot and pjfanning May 20, 2026 13:01
@He-Pin He-Pin marked this pull request as ready for review May 20, 2026 13:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the Intersperse stream stage implementation by fusing the hot-path restInHandler into the GraphStageLogic instance, reducing per-materialization allocations while preserving the existing cold-path handler switch for the first element.

Changes:

  • Refactors Intersperse#createLogic to have the GraphStageLogic directly implement InHandler for the steady-state (hot) path.
  • Keeps a dedicated startInHandler for the first-element (cold) path, then switches the inlet handler to the fused logic instance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@He-Pin He-Pin added this to the 2.0.0-M3 milestone May 20, 2026
Copy link
Copy Markdown
Member

@pjfanning pjfanning left a comment

Choose a reason for hiding this comment

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

lgtm

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