Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Adding BenchmarkClock and nanoseconds convenience accessor for Duration.
- Loading branch information
Showing
4 changed files
with
210 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// Copyright 2002 Ordo One AB | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
// An implementation of a clock suitable for benchmarking using clock_gettime_nsec_np() on macOS | ||
// which is ~2-3 x less overhead. | ||
|
||
// swiftlint:disable line_length identifier_name | ||
|
||
// Largely adopted by Swift's ContinuousClock | ||
// https://github.com/apple/swift/blob/48987de3d3ab228eed4867949795c188759df234/stdlib/public/Concurrency/ContinuousClock.swift#L49 | ||
|
||
#if canImport(Darwin) | ||
import Darwin | ||
#elseif canImport(Glibc) | ||
import Glibc | ||
#else | ||
#error("Unsupported Platform") | ||
#endif | ||
|
||
public struct BenchmarkClock { | ||
/// A continuous point in time used for `BenchmarkClock`. | ||
public struct Instant: Codable, Sendable { | ||
internal var _value: Swift.Duration | ||
|
||
internal init(_value: Swift.Duration) { | ||
self._value = _value | ||
} | ||
} | ||
|
||
public init() {} | ||
} | ||
|
||
public extension Clock where Self == BenchmarkClock { | ||
/// A clock that measures time that always increments but does not stop | ||
/// incrementing while the system is asleep. | ||
/// | ||
/// try await Task.sleep(until: .now + .seconds(3), clock: .continuous) | ||
/// | ||
static var internalUTC: BenchmarkClock { return BenchmarkClock() } | ||
} | ||
|
||
extension BenchmarkClock: Clock { | ||
/// The current continuous instant. | ||
public var now: BenchmarkClock.Instant { | ||
BenchmarkClock.now | ||
} | ||
|
||
/// The minimum non-zero resolution between any two calls to `now`. | ||
public var minimumResolution: Swift.Duration { | ||
#if os(macOS) | ||
return Duration.nanoseconds(1) | ||
#elseif os(Linux) | ||
var resolution = timespec() | ||
|
||
let result = clock_getres(CLOCK_REALTIME, &resolution) | ||
|
||
guard result == 0 else { | ||
fatalError("Failed to get realtime clock resolution in clock_getres(), errno = \(errno)") | ||
} | ||
|
||
let seconds = Int64(resolution.tv_sec) | ||
let attoseconds = Int64(resolution.tv_nsec) * 1_000_000_000 | ||
|
||
return Duration(secondsComponent: seconds, attosecondsComponent: attoseconds) | ||
#else | ||
#error("Unsupported Platform") | ||
#endif | ||
} | ||
|
||
/// The current continuous instant. | ||
public static var now: BenchmarkClock.Instant { | ||
#if os(macOS) | ||
let nanos = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) // to get ns resolution on macOS | ||
|
||
let seconds: UInt64 = nanos / 1_000_000_000 | ||
let attoseconds: UInt64 = (nanos % 1_000_000_000) * 1_000_000_000 | ||
return BenchmarkClock.Instant(_value: Duration(secondsComponent: Int64(seconds), | ||
attosecondsComponent: Int64(attoseconds))) | ||
#elseif os(Linux) | ||
var timespec = timespec() | ||
let result = clock_gettime(CLOCK_REALTIME, ×pec) | ||
|
||
guard result == 0 else { | ||
fatalError("Failed to get current time in clock_gettime(), errno = \(errno)") | ||
} | ||
let seconds = Int64(timespec.tv_sec) | ||
let attoseconds = Int64(timespec.tv_nsec) * 1_000_000_000 | ||
|
||
return BenchmarkClock.Instant(_value: Duration(secondsComponent: Int64(seconds), | ||
attosecondsComponent: Int64(attoseconds))) | ||
#else | ||
#error("Unsupported Platform") | ||
#endif | ||
} | ||
|
||
/// Suspend task execution until a given deadline within a tolerance. | ||
/// If no tolerance is specified then the system may adjust the deadline | ||
/// to coalesce CPU wake-ups to more efficiently process the wake-ups in | ||
/// a more power efficient manner. | ||
/// | ||
/// If the task is canceled before the time ends, this function throws | ||
/// `CancellationError`. | ||
/// | ||
/// This function doesn't block the underlying thread. | ||
public func sleep( | ||
until deadline: Instant, tolerance: Swift.Duration? = nil | ||
) async throws { | ||
try await Task.sleep(until: deadline, tolerance: tolerance, clock: .internalUTC) | ||
} | ||
} | ||
|
||
extension BenchmarkClock.Instant: InstantProtocol { | ||
public static var now: BenchmarkClock.Instant { BenchmarkClock.now } | ||
|
||
public func advanced(by duration: Swift.Duration) -> BenchmarkClock.Instant { | ||
return BenchmarkClock.Instant(_value: _value + duration) | ||
} | ||
|
||
public func duration(to other: BenchmarkClock.Instant) -> Swift.Duration { | ||
other._value - _value | ||
} | ||
|
||
public func hash(into hasher: inout Hasher) { | ||
hasher.combine(_value) | ||
} | ||
|
||
public static func == ( | ||
_ lhs: BenchmarkClock.Instant, _ rhs: BenchmarkClock.Instant | ||
) -> Bool { | ||
return lhs._value == rhs._value | ||
} | ||
|
||
public static func < ( | ||
_ lhs: BenchmarkClock.Instant, _ rhs: BenchmarkClock.Instant | ||
) -> Bool { | ||
return lhs._value < rhs._value | ||
} | ||
|
||
@inlinable | ||
public static func + ( | ||
_ lhs: BenchmarkClock.Instant, _ rhs: Swift.Duration | ||
) -> BenchmarkClock.Instant { | ||
lhs.advanced(by: rhs) | ||
} | ||
|
||
@inlinable | ||
public static func += ( | ||
_ lhs: inout BenchmarkClock.Instant, _ rhs: Swift.Duration | ||
) { | ||
lhs = lhs.advanced(by: rhs) | ||
} | ||
|
||
@inlinable | ||
public static func - ( | ||
_ lhs: BenchmarkClock.Instant, _ rhs: Swift.Duration | ||
) -> BenchmarkClock.Instant { | ||
lhs.advanced(by: .zero - rhs) | ||
} | ||
|
||
@inlinable | ||
public static func -= ( | ||
_ lhs: inout BenchmarkClock.Instant, _ rhs: Swift.Duration | ||
) { | ||
lhs = lhs.advanced(by: .zero - rhs) | ||
} | ||
|
||
@inlinable | ||
public static func - ( | ||
_ lhs: BenchmarkClock.Instant, _ rhs: BenchmarkClock.Instant | ||
) -> Swift.Duration { | ||
rhs.duration(to: lhs) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
public extension Duration { | ||
func nanoseconds() -> Int64 { | ||
return (self.components.seconds * 1_000_000_000) + (self.components.attoseconds / 1_000_000_000) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters