Skip to content

Commit 856524c

Browse files
committed
Optimize flatMapConcat for iterable and empty source.
1 parent 23449f1 commit 856524c

File tree

4 files changed

+117
-32
lines changed

4 files changed

+117
-32
lines changed

akka-stream/src/main/scala/akka/stream/impl/TraversalBuilder.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ package akka.stream.impl
66

77
import scala.collection.immutable.Map.Map1
88
import scala.language.existentials
9-
109
import akka.annotation.{ DoNotInherit, InternalApi }
1110
import akka.stream._
1211
import akka.stream.impl.StreamLayout.AtomicModule
1312
import akka.stream.impl.TraversalBuilder.{ AnyFunction1, AnyFunction2 }
1413
import akka.stream.impl.fusing.GraphStageModule
14+
import akka.stream.impl.fusing.GraphStages.IterableSource
1515
import akka.stream.impl.fusing.GraphStages.SingleSource
1616
import akka.stream.scaladsl.Keep
17+
import akka.stream.stage.GraphStage
1718
import akka.util.OptionVal
1819
import akka.util.unused
1920

@@ -370,6 +371,42 @@ import akka.util.unused
370371
}
371372
}
372373
}
374+
375+
/**
376+
* Try to find `SingleSource` or wrapped such. This is used as a
377+
* performance optimization in FlattenMerge and possibly other places.
378+
*/
379+
def getDirectPushableSource[A >: Null](graph: Graph[SourceShape[A], _]): OptionVal[GraphStage[SourceShape[A]]] = {
380+
graph match {
381+
case single: SingleSource[A] @unchecked => OptionVal.Some(single)
382+
case iterable: IterableSource[A] @unchecked => OptionVal.Some(iterable)
383+
case EmptySource => OptionVal.Some(EmptySource)
384+
case _ =>
385+
graph.traversalBuilder match {
386+
case l: LinearTraversalBuilder =>
387+
// It would be != EmptyTraversal if mapMaterializedValue was used and then we can't optimize.
388+
if ((l.traversalSoFar ne EmptyTraversal) || l.attributes.isAsync) {
389+
OptionVal.None
390+
} else {
391+
l.pendingBuilder match {
392+
case OptionVal.Some(a: AtomicTraversalBuilder) =>
393+
a.module match {
394+
case m: GraphStageModule[_, _] =>
395+
m.stage match {
396+
case single: SingleSource[A] @unchecked => OptionVal.Some(single)
397+
case iterable: IterableSource[A] @unchecked => OptionVal.Some(iterable)
398+
case EmptySource => OptionVal.Some(EmptySource)
399+
case _ => OptionVal.None
400+
}
401+
case _ => OptionVal.None
402+
}
403+
case _ => OptionVal.None
404+
}
405+
}
406+
case _ => OptionVal.None
407+
}
408+
}
409+
}
373410
}
374411

375412
/**

akka-stream/src/main/scala/akka/stream/impl/fusing/StreamOfStreams.scala

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@ import akka.stream.Attributes.SourceLocation
1919
import akka.stream.impl.{ Buffer => BufferImpl }
2020
import akka.stream.impl.ActorSubscriberMessage
2121
import akka.stream.impl.ActorSubscriberMessage.OnError
22+
import akka.stream.impl.EmptySource
2223
import akka.stream.impl.Stages.DefaultAttributes
2324
import akka.stream.impl.SubscriptionTimeoutException
2425
import akka.stream.impl.TraversalBuilder
26+
import akka.stream.impl.fusing.GraphStages.IterableSource
2527
import akka.stream.impl.fusing.GraphStages.SingleSource
2628
import akka.stream.scaladsl._
2729
import akka.stream.stage._
2830
import akka.util.OptionVal
2931
import akka.util.ccompat.JavaConverters._
3032

33+
import scala.annotation.nowarn
34+
3135
/**
3236
* INTERNAL API
3337
*/
@@ -39,11 +43,11 @@ import akka.util.ccompat.JavaConverters._
3943
override def initialAttributes = DefaultAttributes.flattenMerge
4044
override val shape = FlowShape(in, out)
4145

42-
override def createLogic(enclosingAttributes: Attributes) =
43-
new GraphStageLogic(shape) with OutHandler with InHandler {
46+
override def createLogic(enclosingAttributes: Attributes): GraphStageLogic with InHandler with OutHandler =
47+
new GraphStageLogic(shape) with InHandler with OutHandler {
4448
var sources = Set.empty[SubSinkInlet[T]]
45-
var pendingSingleSources = 0
46-
def activeSources = sources.size + pendingSingleSources
49+
var pendingDirectPushSources = 0
50+
def activeSources: Int = sources.size + pendingDirectPushSources
4751

4852
// To be able to optimize for SingleSource without materializing them the queue may hold either
4953
// SubSinkInlet[T] or SingleSource
@@ -60,15 +64,32 @@ import akka.util.ccompat.JavaConverters._
6064
case single: SingleSource[T] @unchecked =>
6165
push(out, single.elem)
6266
removeSource(single)
67+
case iterableSource: IterableSource[T] @unchecked =>
68+
handleIterableSource(iterableSource)
6369
case other =>
6470
throw new IllegalStateException(s"Unexpected source type in queue: '${other.getClass}'")
6571
}
6672
}
6773

74+
@nowarn("msg=deprecated")
75+
private def handleIterableSource(iterableSource: IterableSource[T]): Unit = {
76+
val elements = iterableSource.elements
77+
if (elements.hasDefiniteSize) {
78+
if (elements.isEmpty) {
79+
removeSource(iterableSource)
80+
} else if (elements.size == 1) {
81+
push(out, elements.head)
82+
removeSource(iterableSource)
83+
}
84+
} else {
85+
emitMultiple(out, elements, () => removeSource(iterableSource))
86+
}
87+
}
88+
6889
override def onPush(): Unit = {
6990
val source = grab(in)
7091
addSource(source)
71-
if (activeSources < breadth) tryPull(in)
92+
if (activeSources < breadth && !hasBeenPulled(in)) tryPull(in)
7293
}
7394

7495
override def onUpstreamFinish(): Unit = if (activeSources == 0) completeStage()
@@ -87,47 +108,73 @@ import akka.util.ccompat.JavaConverters._
87108
def addSource(source: Graph[SourceShape[T], M]): Unit = {
88109
// If it's a SingleSource or wrapped such we can push the element directly instead of materializing it.
89110
// Have to use AnyRef because of OptionVal null value.
90-
TraversalBuilder.getSingleSource(source.asInstanceOf[Graph[SourceShape[AnyRef], M]]) match {
91-
case OptionVal.Some(single) =>
92-
if (isAvailable(out) && queue.isEmpty) {
93-
push(out, single.elem.asInstanceOf[T])
94-
} else {
95-
queue.enqueue(single)
96-
pendingSingleSources += 1
97-
}
98-
case _ =>
99-
val sinkIn = new SubSinkInlet[T]("FlattenMergeSink")
100-
sinkIn.setHandler(new InHandler {
101-
override def onPush(): Unit = {
102-
if (isAvailable(out)) {
103-
push(out, sinkIn.grab())
104-
sinkIn.pull()
111+
TraversalBuilder.getDirectPushableSource(source.asInstanceOf[Graph[SourceShape[AnyRef], M]]) match {
112+
case OptionVal.Some(s) =>
113+
s.asInstanceOf[GraphStage[SourceShape[T]]] match {
114+
case single: SingleSource[T] @unchecked =>
115+
if (isAvailable(out) && queue.isEmpty) {
116+
push(out, single.elem)
105117
} else {
106-
queue.enqueue(sinkIn)
118+
queue.enqueue(single)
119+
pendingDirectPushSources += 1
107120
}
108-
}
109-
override def onUpstreamFinish(): Unit = if (!sinkIn.isAvailable) removeSource(sinkIn)
110-
})
111-
sinkIn.pull()
112-
sources += sinkIn
113-
val graph = Source.fromGraph(source).to(sinkIn.sink)
114-
interpreter.subFusingMaterializer.materialize(graph, defaultAttributes = enclosingAttributes)
121+
case iterable: IterableSource[T] @unchecked =>
122+
pendingDirectPushSources += 1
123+
if (isAvailable(out) && queue.isEmpty) {
124+
handleIterableSource(iterable)
125+
} else {
126+
queue.enqueue(iterable)
127+
}
128+
case EmptySource =>
129+
tryPullOrComplete()
130+
case _ =>
131+
addSourceWithMaterialization(source)
132+
}
133+
case _ =>
134+
addSourceWithMaterialization(source)
115135
}
116136
}
117137

138+
private def addSourceWithMaterialization(source: Graph[SourceShape[T], M]): Unit = {
139+
val sinkIn = new SubSinkInlet[T]("FlattenMergeSink")
140+
sinkIn.setHandler(new InHandler {
141+
override def onPush(): Unit = {
142+
if (isAvailable(out)) {
143+
push(out, sinkIn.grab())
144+
sinkIn.pull()
145+
} else {
146+
queue.enqueue(sinkIn)
147+
}
148+
}
149+
override def onUpstreamFinish(): Unit = if (!sinkIn.isAvailable) removeSource(sinkIn)
150+
})
151+
sinkIn.pull()
152+
sources += sinkIn
153+
val graph = Source.fromGraph(source).to(sinkIn.sink)
154+
interpreter.subFusingMaterializer.materialize(graph, defaultAttributes = enclosingAttributes)
155+
}
156+
118157
def removeSource(src: AnyRef): Unit = {
119158
val pullSuppressed = activeSources == breadth
120159
src match {
121160
case sub: SubSinkInlet[T] @unchecked =>
122161
sources -= sub
123-
case _: SingleSource[_] =>
124-
pendingSingleSources -= 1
162+
case _: SingleSource[_] | _: IterableSource[_] =>
163+
pendingDirectPushSources -= 1
125164
case other => throw new IllegalArgumentException(s"Unexpected source type: '${other.getClass}'")
126165
}
127166
if (pullSuppressed) tryPull(in)
128167
if (activeSources == 0 && isClosed(in)) completeStage()
129168
}
130169

170+
private def tryPullOrComplete(): Unit = {
171+
if (activeSources < breadth) {
172+
tryPull(in)
173+
} else if (activeSources == 0 && isClosed(in)) {
174+
completeStage()
175+
}
176+
}
177+
131178
override def postStop(): Unit = sources.foreach(_.cancel())
132179

133180
}

akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ object Source {
272272
apply(new immutable.Iterable[T] {
273273
override def iterator: Iterator[T] = f()
274274
override def toString: String = "() => Iterator"
275+
override def hasDefiniteSize: Boolean = false
275276
})
276277

277278
/**

akka-stream/src/main/scala/akka/stream/stage/GraphStage.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ abstract class AbstractGraphStageWithMaterializedValue[+S <: Shape, M] extends G
101101
* A GraphStage consists of a [[Shape]] which describes its input and output ports and a factory function that
102102
* creates a [[GraphStageLogic]] which implements the processing logic that ties the ports together.
103103
*/
104-
abstract class GraphStage[S <: Shape] extends GraphStageWithMaterializedValue[S, NotUsed] {
104+
abstract class GraphStage[+S <: Shape] extends GraphStageWithMaterializedValue[S, NotUsed] {
105105
final override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, NotUsed) =
106106
(createLogic(inheritedAttributes), NotUsed)
107107

0 commit comments

Comments
 (0)