mirror of
https://github.com/apple/swift-nio.git
synced 2026-05-20 20:30:36 +00:00
## Motivation
The current `scheduleTask` APIs make use of _both_ callbacks and
promises, which leads to confusing semantics. For example, on
cancellation, users are notified in two ways: once via the promise and
once via the callback. Additionally the way the API is structured
results in unavoidable allocations—for the closures and the
promise—which could be avoided if we structured the API differently.
## Modifications
This PR introduces new protocol requirements on `EventLoop`:
```swift
protocol EventLoop {
// ...
@discardableResult
func scheduleCallback(at deadline: NIODeadline, handler: some NIOScheduledCallbackHandler) throws -> NIOScheduledCallback
@discardableResult
func scheduleCallback(in amount: TimeAmount, handler: some NIOScheduledCallbackHandler) throws -> NIOScheduledCallback
func cancelScheduledCallback(_ scheduledCallback: NIOScheduledCallback)
}
```
Default implementations have been provided that call through to
`EventLoop.scheduleTask(in:_:)` to not break existing `EventLoop`
implementations, although this implementation will be (at least) as slow
as using `scheduleTask(in:_:)` directly.
The API is structured to allow for `EventLoop` implementations to
provide a custom implementation, as an optimization point and this PR
provides a custom implementation for `SelectableEventLoop`, so that
`MultiThreadedEventLoopGroup` can benefit from a faster implementation.
Finally, this PR adds benchmarks to measure the performance of setting a
simple timer using both `scheduleTask(in:_:)` and
`scheduleCallback(in:_:)` APIs using a `MultiThreadedEventLoopGroup`.
## Result
A simpler and more coherent API surface.
There is also a small performance benefit for heavy users of this API,
e.g. protocols that make extensive use of timers: when using MTELG to
repeatedly set a timer with the same handler, switching from
`scheduleTask(in:_:)` to `scheduleCallback(in:_:)` reduces almost all
allocations (and amortizes to zero allocations) and is ~twice as fast.
```
MTELG.scheduleCallback(in:_:)
╒═══════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │
╞═══════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Malloc (total) * │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 1109 │
╘═══════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛
MTELG.scheduleTask(in:_:)
╒═══════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │
╞═══════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Malloc (total) * │ 4 │ 4 │ 4 │ 4 │ 4 │ 4 │ 4 │ 576 │
╘═══════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛
```
108 lines
2.5 KiB
Swift
108 lines
2.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
public struct PriorityQueue<Element: Comparable> {
|
|
@usableFromInline
|
|
internal var _heap: Heap<Element>
|
|
|
|
@inlinable
|
|
public init() {
|
|
self._heap = Heap()
|
|
}
|
|
|
|
@inlinable
|
|
public mutating func remove(_ key: Element) {
|
|
self._heap.remove(value: key)
|
|
}
|
|
|
|
@discardableResult
|
|
@inlinable
|
|
public mutating func removeFirst(where shouldBeRemoved: (Element) throws -> Bool) rethrows -> Element? {
|
|
try self._heap.removeFirst(where: shouldBeRemoved)
|
|
}
|
|
|
|
@inlinable
|
|
public mutating func push(_ key: Element) {
|
|
self._heap.append(key)
|
|
}
|
|
|
|
@inlinable
|
|
public func peek() -> Element? {
|
|
self._heap.storage.first
|
|
}
|
|
|
|
@inlinable
|
|
public var isEmpty: Bool {
|
|
self._heap.storage.isEmpty
|
|
}
|
|
|
|
@inlinable
|
|
@discardableResult
|
|
public mutating func pop() -> Element? {
|
|
self._heap.removeRoot()
|
|
}
|
|
|
|
@inlinable
|
|
public mutating func clear() {
|
|
self._heap = Heap()
|
|
}
|
|
}
|
|
|
|
extension PriorityQueue: Equatable {
|
|
@inlinable
|
|
public static func == (lhs: PriorityQueue, rhs: PriorityQueue) -> Bool {
|
|
lhs.count == rhs.count && lhs.elementsEqual(rhs)
|
|
}
|
|
}
|
|
|
|
extension PriorityQueue: Sequence {
|
|
public struct Iterator: IteratorProtocol {
|
|
|
|
@usableFromInline
|
|
var _queue: PriorityQueue<Element>
|
|
|
|
@inlinable
|
|
public init(queue: PriorityQueue<Element>) {
|
|
self._queue = queue
|
|
}
|
|
|
|
@inlinable
|
|
public mutating func next() -> Element? {
|
|
self._queue.pop()
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public func makeIterator() -> Iterator {
|
|
Iterator(queue: self)
|
|
}
|
|
}
|
|
|
|
extension PriorityQueue {
|
|
@inlinable
|
|
public var count: Int {
|
|
self._heap.count
|
|
}
|
|
}
|
|
|
|
extension PriorityQueue: CustomStringConvertible {
|
|
@inlinable
|
|
public var description: String {
|
|
"PriorityQueue(count: \(self.count)): \(Array(self))"
|
|
}
|
|
}
|
|
|
|
extension PriorityQueue: Sendable where Element: Sendable {}
|
|
extension PriorityQueue.Iterator: Sendable where Element: Sendable {}
|