-
Notifications
You must be signed in to change notification settings - Fork 648
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
Significantly improve the performance of EventLoopFuture.{and,when}AllSucceed
.
#943
Conversation
@swift-nio-bot test this please |
Happy to backport this to NIO1 so that Vapor 3 can benefit, by the way. |
Also, thanks to @Mordil for providing a great example with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it to the NIO team to make any real decisions - but I left some thoughts on these changes.
Do you plan to add the fast-track path for {and,when}AllComplete?
@@ -1044,7 +1054,83 @@ extension EventLoopFuture { | |||
/// - on: The `EventLoop` on which the new `EventLoopFuture` callbacks will fire. | |||
/// - Returns: A new `EventLoopFuture` with all of the values fulfilled by the provided futures. | |||
public static func whenAllSucceed(_ futures: [EventLoopFuture<Value>], on eventLoop: EventLoop) -> EventLoopFuture<[Value]> { | |||
return .reduce(into: [], futures, on: eventLoop) { (results, value) in results.append(value) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these methods were using .reduce
, and I can't help but wonder if perhaps the performance boost could have been done in that method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure reduce
and fold
have potential for further optimization, but I don't think their performance will come anywhere close to this bespoke implementation. For example, the argument to fold
is a closure that returns a future. I.e. we'd need to create at least one extra future (several more with the current implementation of fold
, actually) for each element in the array. That alone generates so much overhead that it can't match the performance of bespoke implementations.
There could be a point made about when the performance of a bespoke implementation is needed, however, Vapor & Co. process a ton of already-fulfilled futures where this is significant.
Good idea. Done. Results:
I.e. a nice 30% speedup on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this generally looks really good! I have left a note in the diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you very much, even performance tests 🙌! That looks good to me.
…e.{and,when}AllSucceed`. (apple#943)"
@MrMage Does this mean we can now use again the previous implementation of Vapor’s flatten now that you pushed your changes upstream ? |
Vapor's |
* Cherry-pick "Significantly improve the performance of `EventLoopFuture.{and,when}AllSucceed`. (#943)" * NIO 1 fixes.
~2.7x-12x speedups for
EventLoopFuture.{and,when}AllSucceed
.Motivation:
Projects like Vapor (e.g. https://github.com/vapor/template-kit/blob/master/Sources/TemplateKit/Pipeline/TemplateSerializer.swift) often need to process large arrays of futures where (almost) all futures have already been fulfilled. Such frameworks will benefit tremendously from any possible performance improvement to
EventLoopFuture.{and,when}AllSucceed
.Modifications:
{and,when}AllSucceed
with inspiration from{and,when}AllComplete
.whenAllSucceed
.whenAllSucceed
andcompleteAllSucceed
.Result:
Speedups:
Original runtimes on a Core i7-6700k:
Runtimes without the fast-path for already-completed futures:
(Values for
whenallcomplete
omitted because those methods have not changed.)Runtimes with the fast-path for already-completed futures: