Skip to content
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

feat: [sc-858]: Move Benchmark to DateTime package #4

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}
}
15 changes: 14 additions & 1 deletion Sources/DateTime/InternalUTCClock.swift
Expand Up @@ -29,10 +29,23 @@ public struct InternalUTCClock {
internal init(_value: Swift.Duration) {
self._value = _value
}


public init(secondsComponent: Int64, attosecondsComponent: Int64) {
self._value = Swift.Duration(secondsComponent: secondsComponent, attosecondsComponent: attosecondsComponent)
}

@available(*, deprecated, renamed: "seconds")
public func secondsSinceEpoch() -> Int64 {
return self._value.components.seconds
}

public func seconds() -> Int64 {
return self._value.components.seconds
}

public func attoseconds() -> Int64 {
return self._value.components.attoseconds
}
}

public init() {}
Expand Down
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