-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve traverse
performance through increased specialization
#4408
Comments
What about the case where the applicative is secretly a |
@armanbilge I haven't benchmarked that option, but I would imagine it's not that much faster than the tree + |
I guess we'll have to measure it :) it avoids deconstructing the collection into a tree, which is already a win, and then on top of that it is eagerly-evaluated instead of leaning on |
Sorry for asking if it has been discussed somewhere else already, but I'm just wondering:
Does it have to be secretly a StackSafeMonad? Can it be expressed as a compile-time constraint? I think, that is what the whole idea of type classes is for, after all. |
The problem is that this would require additional overloads of the It's essentially the same thought process we went through with the async The only alternative I can think of in this case would be Oscar's Apply.Strategy approach. This is kind of the same as typecasing, but more explicit for this scenario. I'm definitely open to it if we think it's preferable. |
This is a follow up on #3790, but breaking it out as a separate issue since we've realized that we can do better than what was originally suggested and discussed. Leverages the findings in #4403. Also relates to #3993.
Altogether,
traverse
has three distinct performance scenarios, depending on the nature of the underlying applicativeG
and the underlying traverseF
:StackSafeMonad
, the fastest (by about 3x) strategy is to use the direct left-to-right approach withflatMap
. There are no stack-safety issues, and any underlying short-circuiting is handled by the monad itselfDefer
, then we still need to do the tree branching strategy in order to avoid possible stack issues (just because an applicative forms aDefer
does not mean itsap
is stack safe!), but we no longer need to introduce the extra layer ofEval
wrapping in order to guarantee short-circuitingEval
wrappingThe first scenario corresponds to doing a
traverse
withIO
orEval
, which are bothStackSafeMonad
s. In this case, the directflatMap
is the fastest thing you can do (verified by checking a hand-coded version). The second scenario corresponds to doing aparTraverse
withIO
, where theG
applicative forms aDefer
but does not form aMonad
. In this case, we obviously can't useflatMap
, but the tree branching strategy is optimal anyway because we want to balance the parallel scheduling. We can rely on theG
itself to handle short-circuiting (to the extent that we get it withinpar
) because ofDefer
. The third scenario corresponds to doing atraverse
with something likeOption
, where we aren't stack safe and we can't form aDefer
, so we just need to do the pessimistic tree branching (for stack safety) andEval
wrapping (for short circuiting).Right now, all
Seq
-based traversals delegate totraverseViaChain
, which strictly implements the third (most pessimistic) scenario. What we want here are two additional implementations corresponding to the first and second scenarios (the second is very close to the third, just without all theEval.later
s), and the swapping between them will be based on runtime type casing on theApplicative[G]
instance.The text was updated successfully, but these errors were encountered: