Rewrote parTraverseN and parTraverseN_ for better performance - #4451
Rewrote parTraverseN and parTraverseN_ for better performance#4451djspiewak wants to merge 35 commits into
parTraverseN and parTraverseN_ for better performance#4451Conversation
|
Pros and cons on performance, though I think it's possible to do better here. It's a little bit slower than the previous implementation in the happy path, but it's several orders of magnitude faster in the error path so I'll call that a win. BeforeAfter |
|
So I haven't golfed the failure down yet, but it really looks like we're hitting a bug in Scala.js, probably stemming from the "null safe" test. @durban you may be amused I think we could just remove the null safe test now since we're not using an |
|
Well, "amused" is one word for it :-) So it's not a bug in Scala.js, as in, it behaves as documented: dereferencing |
|
Well that's fun. I actually thought we had some special checking for when the |
|
That we do check. It's this line: https://github.com/typelevel/cats-effect/blob/series/3.x/core/shared/src/main/scala/cats/effect/IO.scala#L2024 (and |
|
Ahhhhhhh that makes sense. Okay, by that token, I think it's fair to say that a lot of our combinators just aren't |
|
It's annoying, because in Scala they are null safe. (The test passed before, it just failed on JS.) We'd have to do something like this (everywhere), to make it work on JS: def combinator(fa: F[A], ...) = {
if (fa eq null) throw new NullPointerException
}Which is (1) annoying, (2) very redundant, except on Scala.js, and (3) apparently has performance problems in Scala.js (or maybe that's only the linker setting?). I don't propose we do this. There is a Scala.js linker setting which fixes the problem. In Scala and Scala Native it works by default. |
|
(Just some context about the |
|
Could this fix hit 3.6.4? |
There are a couple failing tests related to early termination that I'm still trying to track down. Am trying to find the spare time needed to push on it. Help definitely welcome! Otherwise I'll probably get to it within the next few weeks. Sorry :( |
|
I see that last CI is green, do you mean that you want to reintroduce the tests removed in this commit? 599b790 |
fc113cb to
584ce3b
Compare
|
Tick the box to add this pull request to the merge queue (same as
|
|
Tick the box to add this pull request to the merge queue (same as
|
reardonj
left a comment
There was a problem hiding this comment.
I think I found the issue with the "interrupt never on error" test.
…scala Co-authored-by: Justin Reardon <me@jmreardon.com>
|
@durban I think we might be ready! (again) |
|
@djspiewak, the #4434 mentions the internal |
reardonj
left a comment
There was a problem hiding this comment.
I don't see any other issues. A few nitpicks.
|
Both good calls! Fixing… |
There was a problem hiding this comment.
@djspiewak I've looked at parTraverseN_, left some comments. Nothing critical, though the error handling one would be good to fix. I'll get to parTraverseN soon (for some value of "soon").
|
@durban Btw I thought a bit more about your "what if we just used a Given how much of the train is backed up behind this PR, I'd like to push it through, but I'll start another branch experimenting with the queue approach. Maybe in a few years we can land that one… :P |
|
Update: I did a quick PoC and it's like, drastically simpler to use a queue explicitly. Also I can share the core machinery between the two functions, which feels absolutely worth it. |
durban
left a comment
There was a problem hiding this comment.
I've looked at parTraverseN, have one concern about a possible deadlock.
| val target = 0.until(100000).toList | ||
| val test = target.parTraverseN(2)(_ => IO.raiseError(TestException)) | ||
|
|
||
| test.attempt.as(ok).timeoutTo(500.millis, IO(false must beTrue)) |
There was a problem hiding this comment.
Just to check if I'm understanding this after all this time: the false must beTrue always fails, so this checks that the test finishes in at most 500ms, right?
|
@djspiewak Yeah, doing the queue later sounds fine. |
durban
left a comment
There was a problem hiding this comment.
LGTM.
I have one final question as this PR had multiple different versions: was this final-final version measured to improve performance?
|
It was not. I'll do that test before I merge |
This shifts to a fully bespoke implementation of
parTraverseNand such. There are a few things left to clean up, such as a few more tests and running some comparative benchmarks, but early results are very promising. In particular, the failure case from #4434 appears to be around two to three orders of magnitude faster with this implementation (which makes sense, since it handles early abort correctly). Kudos to @SystemFw for the core idea which makes this possible.One of the things I'm doing here is giving up entirely on universal fairness and merely focusing on in-batch fairness. A simpler way of saying this is that we are hardened against head of line blocking, both for actions and cancelation.
Fixes #4434