Skip to content

Commit

Permalink
feat: [sc-858]: Move Benchmark to DateTime package (#4)
Browse files Browse the repository at this point in the history
* Adding BenchmarkClock and nanoseconds convenience accessor for Duration.
  • Loading branch information
hassila committed Nov 29, 2022
1 parent ccdb50c commit cbfca20
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 6 deletions.
8 changes: 7 additions & 1 deletion Benchmarks/Clocks/Clocks.swift
Expand Up @@ -15,7 +15,7 @@ import BenchmarkSupport
func benchmarks() {

Benchmark.defaultDesiredDuration = .seconds(2)
Benchmark.defaultDesiredIterations = 1_000
Benchmark.defaultDesiredIterations = 10_000
Benchmark.defaultThroughputScalingFactor = .kilo

Benchmark("InternalUTCClock.now") { benchmark in
Expand All @@ -24,6 +24,12 @@ func benchmarks() {
}
}

Benchmark("BenchmarkClock.now") { benchmark in
for _ in 0 ..< benchmark.throughputScalingFactor.rawValue {
BenchmarkSupport.blackHole(BenchmarkClock.now)
}
}

Benchmark("Foundation.Date") { benchmark in
for _ in 0 ..< benchmark.throughputScalingFactor.rawValue {
BenchmarkSupport.blackHole(Foundation.Date())
Expand Down
178 changes: 178 additions & 0 deletions Sources/DateTime/BenchmarkClock.swift
@@ -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, &timespec)

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)
}
}
5 changes: 5 additions & 0 deletions Sources/DateTime/Convenience.swift
@@ -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)
}
}
25 changes: 20 additions & 5 deletions Tests/DateTimeTests/DateTime.swift
Expand Up @@ -12,15 +12,30 @@ import XCTest
final class DateTimeTests: XCTestCase {
func testExample() throws {
let time = InternalUTCClock().measure {
print("Time now: \(InternalUTCClock.now)")
print("Time now: \(InternalUTCClock.now._value.components)")
print("Resolution: \(InternalUTCClock().minimumResolution)")
print("InternalUTCClock Time now: \(InternalUTCClock.now)")
print("InternalUTCClock Time now: \(InternalUTCClock.now._value.components)")
print("InternalUTCClock Resolution: \(InternalUTCClock().minimumResolution)")
}
print("Elapsed time in nanoseconds \(time.components.attoseconds / 1_000_000_000)")
print("InternalUTCClock Elapsed time in nanoseconds \(time.components.attoseconds / 1_000_000_000)")
let time2 = InternalUTCClock().measure {
_ = 47
}
print("Elapsed time in nanoseconds for empty closure \(time2.components.attoseconds / 1_000_000_000)")
print("InternalUTCClock Elapsed time in nanoseconds for empty closure \(time2.components.attoseconds / 1_000_000_000)")
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
}
func testBenchmarkClock() throws {
let time = BenchmarkClock().measure {
print("BenchmarkClock Time now: \(BenchmarkClock.now)")
print("BenchmarkClock Time now: \(BenchmarkClock.now._value.components)")
print("BenchmarkClock Resolution: \(BenchmarkClock().minimumResolution)")
}
print("BenchmarkClock Elapsed time in nanoseconds \(time.components.attoseconds / 1_000_000_000)")
let time2 = BenchmarkClock().measure {
_ = 47
}
print("BenchmarkClock Elapsed time in nanoseconds for empty closure \(time2.components.attoseconds / 1_000_000_000)")
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
Expand Down

0 comments on commit cbfca20

Please sign in to comment.