-
Notifications
You must be signed in to change notification settings - Fork 644
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
Convert ScheduledTask
to a struct to reduce allocations for scheduling
#2010
Convert ScheduledTask
to a struct to reduce allocations for scheduling
#2010
Conversation
075586f
to
8f0219b
Compare
@swift-nio-bot test perf please |
performance reportbuild id: 88 timestamp: Tue Dec 14 09:07:41 UTC 2021 results
comparison
significant differences found |
8b6d27f
to
5b75a75
Compare
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
7e9b916
to
b77f283
Compare
Sources/NIOEmbedded/Embedded.swift
Outdated
@@ -63,6 +67,7 @@ public final class EmbeddedEventLoop: EventLoop { | |||
/// The current "time" for this event loop. This is an amount in nanoseconds. | |||
/* private but tests */ internal var _now: NIODeadline = .uptimeNanoseconds(0) | |||
|
|||
private var scheduledTaskCounter = NIOAtomic.makeAtomic(value: UInt64(0)) |
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.
It's a shame that this now regresses a bunch of allocation counting tests that use EmbeddedEventLoop
(and will no doubt regress allocation counting tests in other swift-nio-* packages). I wonder if we should make the counter for embedded loops global (or static) to reduce the noise of this one.
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.
Actually EmbeddedEventLoop
isn't thread safe, can we just use a UInt64
?
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.
We can, and probably should.
@swift-nio-bot test perf please |
performance reportbuild id: 89 timestamp: Tue Dec 14 13:09:22 UTC 2021 results
comparison
significant differences found |
397dbdf
to
419ced9
Compare
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
@swift-server-bot test this please |
419ced9
to
15b1bdd
Compare
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.
Awesome!
### Motivation: In my previous PR apple#2009, I added baseline performance and allocation tests around `scheduleTask` and `execute`. After analysing, the various allocations that happen when scheduling a task there were only a few that could be optimized away potentially. ### Modifications: This PR converts the `ScheduledTask` class to a struct which will reduce the number of allocations for scheduling tasks by 1. The only thing that needs to be worked around when converting to a struct is giving it an identity so that we can implement `Equatable` conformance properly. I explored two options. First, using an `ObjectIdentifier` passed to the init. Second, using an atomic counter per EventLoop. I went with the latter since the former requires an additional allocation in the case of calling `execute` ### Result: `scheduleTask` and `execute` require one less allocation
15b1bdd
to
584c6aa
Compare
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.
Really nice change, well done!
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: In my previous PR #2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR #2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes apple#1541 ### Modifications: In my previous PR apple#2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
### Motivation: When scheduling tasks with the same deadline the current order of execution is undefined. Fixes #1541 ### Modifications: In my previous PR #2010, I added an internal id to every `ScheduledTask` to give them an identity for cancellation purposes. In this PR, I am now using the same id to also ensure that the execution of tasks with the same deadline is the same as the order they were scheduled in. ### Result: `ScheduledTask`s are now executed in their scheduled order when they have the same deadline.
Motivation:
In my previous PR #2009, I added baseline performance and allocation tests around
scheduleTask
andexecute
. After analysing, the various allocations that happen when scheduling a task there were only a few that could be optimized away potentially.Modifications:
This PR converts the
ScheduledTask
class to a struct which will reduce the number of allocations for scheduling tasks by 1. The only thing that needs to be worked around when converting to a struct is giving it an identity so that we can implementEquatable
conformance properly. I explored two options. First, using anObjectIdentifier
passed to the init. Second, using an atomic counter per EventLoop. I went with the latter since the former requires an additional allocation in the case of callingexecute
Result:
scheduleTask
andexecute
require one less allocation