Merge pull request #14 from ktoso/benchmarks

+simple benchmarks to have a gut feeling how mutating and the CoW cos…
This commit is contained in:
Konrad `ktoso` Malawski 2020-07-27 18:06:16 +09:00 committed by GitHub
commit c78c490045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1745 additions and 1 deletions

View File

@ -11,11 +11,30 @@ let package = Package(
name: "Baggage",
dependencies: []
),
// ==== --------------------------------------------------------------------------------------------------------
// MARK: Tests
.testTarget(
name: "BaggageTests",
dependencies: [
"Baggage"
]
)
),
// ==== --------------------------------------------------------------------------------------------------------
// MARK: Performance / Benchmarks
.target(
name: "Benchmarks",
dependencies: [
"Baggage",
"SwiftBenchmarkTools",
]
),
.target(
name: "SwiftBenchmarkTools",
dependencies: []
),
]
)

View File

@ -0,0 +1,189 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Baggage
import Dispatch
import class Foundation.NSLock
import SwiftBenchmarkTools
public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: "Read only" context passing around
BenchmarkInfo(
name: "BaggagePassingBenchmarks.pass_async_empty_100_000 ",
runFunction: { _ in
let context = BaggageContext()
pass_async(context: context, times: 100_000)
},
tags: [],
setUpFunction: { setUp() },
tearDownFunction: tearDown
),
BenchmarkInfo(
name: "BaggagePassingBenchmarks.pass_async_smol_100_000 ",
runFunction: { _ in
var context = BaggageContext()
context.k1 = "one"
context.k2 = "two"
context.k3 = "three"
context.k4 = "four"
pass_async(context: context, times: 100_000)
},
tags: [],
setUpFunction: { setUp() },
tearDownFunction: tearDown
),
BenchmarkInfo(
name: "BaggagePassingBenchmarks.pass_async_small_nonconst_100_000",
runFunction: { _ in
var context = BaggageContext()
context.k1 = "\(Int.random(in: 1 ... Int.max))"
context.k2 = "\(Int.random(in: 1 ... Int.max))"
context.k3 = "\(Int.random(in: 1 ... Int.max))"
context.k4 = "\(Int.random(in: 1 ... Int.max))"
pass_async(context: context, times: 100_000)
},
tags: [],
setUpFunction: { setUp() },
tearDownFunction: tearDown
),
// ==== ------------------------------------------------------------------------------------------------------------
// MARK: Passing & Mutating
// Since the context is backed by a dictionary (and nothing else) we rely on its CoW semantics, those writes cause copies
// whilst the previous benchmarks which are read-only do not cause copies of the underlying storage (dictionary).
BenchmarkInfo(
name: "BaggagePassingBenchmarks.pass_mut_async_small_100_000 ",
runFunction: { _ in
var context = BaggageContext()
context.k1 = "\(Int.random(in: 1 ... Int.max))"
context.k2 = "\(Int.random(in: 1 ... Int.max))"
context.k3 = "\(Int.random(in: 1 ... Int.max))"
context.k4 = "\(Int.random(in: 1 ... Int.max))"
pass_mut_async(context: context, times: 100_000)
},
tags: [],
setUpFunction: { setUp() },
tearDownFunction: tearDown
),
]
private func setUp() {
// ...
}
private func tearDown() {
// ...
}
@inline(never)
func pass_async(context: BaggageContext, times remaining: Int) {
let latch = CountDownLatch(from: 1)
func pass_async0(context: BaggageContext, times remaining: Int) {
if remaining == 0 {
latch.countDown()
}
DispatchQueue.global().async {
pass_async0(context: context, times: remaining - 1)
}
}
pass_async0(context: context, times: remaining - 1)
latch.wait()
}
@inline(never)
func pass_mut_async(context: BaggageContext, times remaining: Int) {
var context = context
let latch = CountDownLatch(from: 1)
func pass_async0(context: BaggageContext, times remaining: Int) {
if remaining == 0 {
latch.countDown()
}
DispatchQueue.global().async {
// mutate the context
var context = context
context.passCounter = remaining
pass_async0(context: context, times: remaining - 1)
}
}
context.passCounter = remaining
pass_async0(context: context, times: remaining - 1)
latch.wait()
}
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Baggage Keys
private enum TestPassCounterKey: BaggageContextKey {
typealias Value = Int
}
private enum TestK1: BaggageContextKey {
typealias Value = String
}
private enum TestK2: BaggageContextKey {
typealias Value = String
}
private enum TestK3: BaggageContextKey {
typealias Value = String
}
private enum TestK4: BaggageContextKey {
typealias Value = String
}
private enum TestKD1: BaggageContextKey {
typealias Value = [String: String]
}
extension BaggageContext {
fileprivate var passCounter: TestPassCounterKey.Value {
get { self[TestPassCounterKey.self] ?? 0 }
set { self[TestPassCounterKey.self] = newValue }
}
fileprivate var k1: TestK1.Value? {
get { self[TestK1.self] }
set { self[TestK1.self] = newValue }
}
fileprivate var k2: TestK2.Value? {
get { self[TestK2.self] }
set { self[TestK2.self] = newValue }
}
fileprivate var k3: TestK3.Value? {
get { self[TestK3.self] }
set { self[TestK3.self] = newValue }
}
fileprivate var k4: TestK4.Value? {
get { self[TestK4.self] }
set { self[TestK4.self] = newValue }
}
fileprivate var kd1: TestKD1.Value? {
get { self[TestKD1.self] }
set { self[TestKD1.self] = newValue }
}
}

View File

@ -0,0 +1,253 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#else
import Glibc
#endif
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: CountDownLatch
internal class CountDownLatch {
private var counter: Int
private let condition: _Condition
private let lock: _Mutex
init(from: Int) {
self.counter = from
self.condition = _Condition()
self.lock = _Mutex()
}
/// Returns previous value before the decrement was issued.
func countDown() {
self.lock.synchronized {
self.counter -= 1
if self.counter == 0 {
self.condition.signalAll()
}
}
}
var count: Int {
self.lock.synchronized {
self.counter
}
}
func wait() {
self.lock.synchronized {
while true {
if self.counter == 0 {
return // done
}
self.condition.wait(lock)
}
}
}
}
extension CountDownLatch: CustomStringConvertible {
public var description: String {
"CountDownLatch(remaining:\(self.count)"
}
}
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Condition
final class _Condition {
@usableFromInline
var condition: pthread_cond_t = pthread_cond_t()
public init() {
let error = pthread_cond_init(&self.condition, nil)
switch error {
case 0:
return
default:
fatalError("Condition could not be created: \(error)")
}
}
deinit {
pthread_cond_destroy(&condition)
}
@inlinable
public func wait(_ mutex: _Mutex) {
let error = pthread_cond_wait(&self.condition, &mutex.mutex)
switch error {
case 0:
return
case EPERM:
fatalError("Wait failed, mutex is not owned by this thread")
case EINVAL:
fatalError("Wait failed, condition is not valid")
default:
fatalError("Wait failed with unspecified error: \(error)")
}
}
// @inlinable
// public func wait(_ mutex: _Mutex) -> Bool {
// let error = withUnsafePointer(to: time) { p -> Int32 in
// #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
// return pthread_cond_timedwait_relative_np(&condition, &mutex.mutex, p)
// #else
// return pthread_cond_timedwait(&condition, &mutex.mutex, p)
// #endif
// }
//
// switch error {
// case 0:
// return true
// case ETIMEDOUT:
// return false
// case EPERM:
// fatalError("Wait failed, mutex is not owned by this thread")
// case EINVAL:
// fatalError("Wait failed, condition is not valid")
// default:
// fatalError("Wait failed with unspecified error: \(error)")
// }
// }
@inlinable
public func signal() {
let error = pthread_cond_signal(&self.condition)
switch error {
case 0:
return
case EINVAL:
fatalError("Signal failed, condition is not valid")
default:
fatalError("Signal failed with unspecified error: \(error)")
}
}
@inlinable
public func signalAll() {
let error = pthread_cond_broadcast(&self.condition)
switch error {
case 0:
return
case EINVAL:
fatalError("Signal failed, condition is not valid")
default:
fatalError("Signal failed with unspecified error: \(error)")
}
}
}
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Mutex
final class _Mutex {
@usableFromInline
var mutex: pthread_mutex_t = pthread_mutex_t()
public init() {
var attr: pthread_mutexattr_t = pthread_mutexattr_t()
pthread_mutexattr_init(&attr)
pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_RECURSIVE))
let error = pthread_mutex_init(&self.mutex, &attr)
pthread_mutexattr_destroy(&attr)
switch error {
case 0:
return
default:
fatalError("Could not create mutex: \(error)")
}
}
deinit {
pthread_mutex_destroy(&mutex)
}
@inlinable
public func lock() {
let error = pthread_mutex_lock(&self.mutex)
switch error {
case 0:
return
case EDEADLK:
fatalError("Mutex could not be acquired because it would have caused a deadlock")
default:
fatalError("Failed with unspecified error: \(error)")
}
}
@inlinable
public func unlock() {
let error = pthread_mutex_unlock(&self.mutex)
switch error {
case 0:
return
case EPERM:
fatalError("Mutex could not be unlocked because it is not held by the current thread")
default:
fatalError("Unlock failed with unspecified error: \(error)")
}
}
@inlinable
public func tryLock() -> Bool {
let error = pthread_mutex_trylock(&self.mutex)
switch error {
case 0:
return true
case EBUSY:
return false
case EDEADLK:
fatalError("Mutex could not be acquired because it would have caused a deadlock")
default:
fatalError("Failed with unspecified error: \(error)")
}
}
@inlinable
public func synchronized<A>(_ f: () -> A) -> A {
self.lock()
defer {
unlock()
}
return f()
}
@inlinable
public func synchronized<A>(_ f: () throws -> A) throws -> A {
self.lock()
defer {
unlock()
}
return try f()
}
}

View File

@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SwiftBenchmarkTools
assert({
print("===========================================================================")
print("= !! YOU ARE RUNNING BENCHMARKS IN DEBUG MODE !! =")
print("= When running on the command line, use: `swift run -c release` =")
print("===========================================================================")
return true
}())
@inline(__always)
private func registerBenchmark(_ bench: BenchmarkInfo) {
registeredBenchmarks.append(bench)
}
@inline(__always)
private func registerBenchmark(_ benches: [BenchmarkInfo]) {
benches.forEach(registerBenchmark)
}
@inline(__always)
private func registerBenchmark(_ name: String, _ function: @escaping (Int) -> Void, _ tags: [BenchmarkCategory]) {
registerBenchmark(BenchmarkInfo(name: name, runFunction: function, tags: tags))
}
registerBenchmark(BaggagePassingBenchmarks)
main()

View File

@ -0,0 +1,256 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===--- ArgParse.swift ---------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
enum ArgumentError: Error {
case missingValue(String)
case invalidType(value: String, type: String, argument: String?)
case unsupportedArgument(String)
}
extension ArgumentError: CustomStringConvertible {
public var description: String {
switch self {
case .missingValue(let key):
return "missing value for '\(key)'"
case .invalidType(let value, let type, let argument):
return (argument == nil)
? "'\(value)' is not a valid '\(type)'"
: "'\(value)' is not a valid '\(type)' for '\(argument!)'"
case .unsupportedArgument(let argument):
return "unsupported argument '\(argument)'"
}
}
}
/// Type-checked parsing of the argument value.
///
/// - Returns: Typed value of the argument converted using the `parse` function.
///
/// - Throws: `ArgumentError.invalidType` when the conversion fails.
func checked<T>(
_ parse: (String) throws -> T?,
_ value: String,
argument: String? = nil
) throws -> T {
if let t = try parse(value) { return t }
var type = "\(T.self)"
if type.starts(with: "Optional<") {
let s = type.index(after: type.firstIndex(of: "<")!)
let e = type.index(before: type.endIndex) // ">"
type = String(type[s ..< e]) // strip Optional< >
}
throw ArgumentError.invalidType(
value: value, type: type, argument: argument
)
}
/// Parser that converts the program's command line arguments to typed values
/// according to the parser's configuration, storing them in the provided
/// instance of a value-holding type.
class ArgumentParser<U> {
private var result: U
private var validOptions: [String] {
self.arguments.compactMap { $0.name }
}
private var arguments: [Argument] = []
private let programName: String = {
// Strip full path from the program name.
let r = CommandLine.arguments[0].reversed()
let ss = r[r.startIndex ..< (r.firstIndex(of: "/") ?? r.endIndex)]
return String(ss.reversed())
}()
private var positionalArgs = [String]()
private var optionalArgsMap = [String: String]()
/// Argument holds the name of the command line parameter, its help
/// desciption and a rule that's applied to process it.
///
/// The the rule is typically a value processing closure used to convert it
/// into given type and storing it in the parsing result.
///
/// See also: addArgument, parseArgument
struct Argument {
let name: String?
let help: String?
let apply: () throws -> Void
}
/// ArgumentParser is initialized with an instance of a type that holds
/// the results of the parsing of the individual command line arguments.
init(into result: U) {
self.result = result
self.arguments += [
Argument(
name: "--help", help: "show this help message and exit",
apply: self.printUsage
),
]
}
private func printUsage() {
guard let _ = optionalArgsMap["--help"] else { return }
let space = " "
let maxLength = self.arguments.compactMap { $0.name?.count }.max()!
let padded = { (s: String) in
" \(s)\(String(repeating: space, count: maxLength - s.count)) "
}
let f: (String, String) -> String = {
"\(padded($0))\($1)"
.split(separator: "\n")
.joined(separator: "\n" + padded(""))
}
let positional = f("TEST", "name or number of the benchmark to measure")
let optional = self.arguments.filter { $0.name != nil }
.map { f($0.name!, $0.help ?? "") }
.joined(separator: "\n")
print(
"""
usage: \(self.programName) [--argument=VALUE] [TEST [TEST ...]]
positional arguments:
\(positional)
optional arguments:
\(optional)
""")
exit(0)
}
/// Parses the command line arguments, returning the result filled with
/// specified argument values or report errors and exit the program if
/// the parsing fails.
public func parse() -> U {
do {
try self.parseArgs() // parse the argument syntax
try self.arguments.forEach { try $0.apply() } // type-check and store values
return self.result
} catch let error as ArgumentError {
fputs("error: \(error)\n", stderr)
exit(1)
} catch {
fflush(stdout)
fatalError("\(error)")
}
}
/// Using CommandLine.arguments, parses the structure of optional and
/// positional arguments of this program.
///
/// We assume that optional switch args are of the form:
///
/// --opt-name[=opt-value]
/// -opt-name[=opt-value]
///
/// with `opt-name` and `opt-value` not containing any '=' signs. Any
/// other option passed in is assumed to be a positional argument.
///
/// - Throws: `ArgumentError.unsupportedArgument` on failure to parse
/// the supported argument syntax.
private func parseArgs() throws {
// For each argument we are passed...
for arg in CommandLine.arguments[1 ..< CommandLine.arguments.count] {
// If the argument doesn't match the optional argument pattern. Add
// it to the positional argument list and continue...
if !arg.starts(with: "-") {
self.positionalArgs.append(arg)
continue
}
// Attempt to split it into two components separated by an equals sign.
let components = arg.split(separator: "=")
let optionName = String(components[0])
guard self.validOptions.contains(optionName) else {
throw ArgumentError.unsupportedArgument(arg)
}
var optionVal: String
switch components.count {
case 1: optionVal = ""
case 2: optionVal = String(components[1])
default:
// If we do not have two components at this point, we can not have
// an option switch. This is an invalid argument. Bail!
throw ArgumentError.unsupportedArgument(arg)
}
self.optionalArgsMap[optionName] = optionVal
}
}
/// Add a rule for parsing the specified argument.
///
/// Stores the type-erased invocation of the `parseArgument` in `Argument`.
///
/// Parameters:
/// - name: Name of the command line argument. E.g.: `--opt-arg`.
/// `nil` denotes positional arguments.
/// - property: Property on the `result`, to store the value into.
/// - defaultValue: Value used when the command line argument doesn't
/// provide one.
/// - help: Argument's description used when printing usage with `--help`.
/// - parser: Function that converts the argument value to given type `T`.
public func addArgument<T>(
_ name: String?,
_ property: WritableKeyPath<U, T>,
defaultValue: T? = nil,
help: String? = nil,
parser: @escaping (String) throws -> T? = { _ in nil }
) {
self.arguments.append(
Argument(name: name, help: help)
{ try self.parseArgument(name, property, defaultValue, parser) }
)
}
/// Process the specified command line argument.
///
/// For optional arguments that have a value we attempt to convert it into
/// given type using the supplied parser, performing the type-checking with
/// the `checked` function.
/// If the value is empty the `defaultValue` is used instead.
/// The typed value is finally stored in the `result` into the specified
/// `property`.
///
/// For the optional positional arguments, the [String] is simply assigned
/// to the specified property without any conversion.
///
/// See `addArgument` for detailed parameter descriptions.
private func parseArgument<T>(
_ name: String?,
_ property: WritableKeyPath<U, T>,
_ defaultValue: T?,
_ parse: (String) throws -> T?
) throws {
if let name = name, let value = optionalArgsMap[name] {
guard !value.isEmpty || defaultValue != nil
else { throw ArgumentError.missingValue(name) }
self.result[keyPath: property] = value.isEmpty
? defaultValue!
: try checked(parse, value, argument: name)
} else if name == nil {
self.result[keyPath: property] = self.positionalArgs as! T
}
}
}

View File

@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
//
// Based on: https://github.com/apple/swift/tree/cf53143a47278c2a465409a67376642515956777/benchmark/utils
//
//===----------------------------------------------------------------------===//
public enum BenchmarkCategory: String {
// Most benchmarks are assumed to be "stable" and will be regularly tracked at
// each commit. A handful may be marked unstable if continually tracking them is
// counterproductive.
case unstable
// Explicit skip marker
case skip
}

View File

@ -0,0 +1,230 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
//
// Based on: https://github.com/apple/swift/tree/cf53143a47278c2a465409a67376642515956777/benchmark/utils
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import Glibc
#else
import Darwin
#endif
extension BenchmarkCategory: CustomStringConvertible {
public var description: String {
self.rawValue
}
}
extension BenchmarkCategory: Comparable {
public static func < (lhs: BenchmarkCategory, rhs: BenchmarkCategory) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
public struct BenchmarkPlatformSet: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let darwin = BenchmarkPlatformSet(rawValue: 1 << 0)
public static let linux = BenchmarkPlatformSet(rawValue: 1 << 1)
public static var currentPlatform: BenchmarkPlatformSet {
#if os(Linux)
return .linux
#else
return .darwin
#endif
}
public static var allPlatforms: BenchmarkPlatformSet {
[.darwin, .linux]
}
}
public struct BenchmarkInfo {
/// The name of the benchmark that should be displayed by the harness.
public var name: String
/// Shadow static variable for runFunction.
private var _runFunction: (Int) -> Void
/// A function that invokes the specific benchmark routine.
public var runFunction: ((Int) -> Void)? {
if !self.shouldRun {
return nil
}
return self._runFunction
}
/// A set of category tags that describe this benchmark. This is used by the
/// harness to allow for easy slicing of the set of benchmarks along tag
/// boundaries, e.x.: run all string benchmarks or ref count benchmarks, etc.
public var tags: Set<BenchmarkCategory>
/// The platforms that this benchmark supports. This is an OptionSet.
private var unsupportedPlatforms: BenchmarkPlatformSet
/// Shadow variable for setUpFunction.
private var _setUpFunction: (() -> Void)?
/// An optional function that if non-null is run before benchmark samples
/// are timed.
public var setUpFunction: (() -> Void)? {
if !self.shouldRun {
return nil
}
return self._setUpFunction
}
/// Shadow static variable for computed property tearDownFunction.
private var _tearDownFunction: (() -> Void)?
/// An optional function that if non-null is run after samples are taken.
public var tearDownFunction: (() -> Void)? {
if !self.shouldRun {
return nil
}
return self._tearDownFunction
}
public var legacyFactor: Int?
public init(
name: String, runFunction: @escaping (Int) -> Void, tags: [BenchmarkCategory],
setUpFunction: (() -> Void)? = nil,
tearDownFunction: (() -> Void)? = nil,
unsupportedPlatforms: BenchmarkPlatformSet = [],
legacyFactor: Int? = nil
) {
self.name = name
self._runFunction = runFunction
self.tags = Set(tags)
self._setUpFunction = setUpFunction
self._tearDownFunction = tearDownFunction
self.unsupportedPlatforms = unsupportedPlatforms
self.legacyFactor = legacyFactor
}
/// Returns true if this benchmark should be run on the current platform.
var shouldRun: Bool {
!self.unsupportedPlatforms.contains(.currentPlatform)
}
}
extension BenchmarkInfo: Comparable {
public static func < (lhs: BenchmarkInfo, rhs: BenchmarkInfo) -> Bool {
lhs.name < rhs.name
}
public static func == (lhs: BenchmarkInfo, rhs: BenchmarkInfo) -> Bool {
lhs.name == rhs.name
}
}
extension BenchmarkInfo: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
}
// Linear function shift register.
//
// This is just to drive benchmarks. I don't make any claim about its
// strength. According to Wikipedia, it has the maximal period for a
// 32-bit register.
struct LFSR {
// Set the register to some seed that I pulled out of a hat.
var lfsr: UInt32 = 0xB789_78E7
mutating func shift() {
self.lfsr = (self.lfsr >> 1) ^ (UInt32(bitPattern: -Int32(self.lfsr & 1)) & 0xD000_0001)
}
mutating func randInt() -> Int64 {
var result: UInt32 = 0
for _ in 0 ..< 32 {
result = (result << 1) | (self.lfsr & 1)
self.shift()
}
return Int64(bitPattern: UInt64(result))
}
}
var lfsrRandomGenerator = LFSR()
// Start the generator from the beginning
public func SRand() {
lfsrRandomGenerator = LFSR()
}
public func Random() -> Int64 {
lfsrRandomGenerator.randInt()
}
@inlinable // FIXME(inline-always)
@inline(__always)
public func CheckResults(
_ resultsMatch: Bool,
file: StaticString = #file,
function: StaticString = #function,
line: Int = #line
) {
guard _fastPath(resultsMatch) else {
print("Incorrect result in \(function), \(file):\(line)")
abort()
}
}
public func False() -> Bool { false }
/// This is a dummy protocol to test the speed of our protocol dispatch.
public protocol SomeProtocol { func getValue() -> Int }
struct MyStruct: SomeProtocol {
init() {}
func getValue() -> Int { 1 }
}
public func someProtocolFactory() -> SomeProtocol { MyStruct() }
// Just consume the argument.
// It's important that this function is in another module than the tests
// which are using it.
@inline(never)
public func blackHole<T>(_: T) {}
// Return the passed argument without letting the optimizer know that.
@inline(never)
public func identity<T>(_ x: T) -> T {
x
}
// Return the passed argument without letting the optimizer know that.
// It's important that this function is in another module than the tests
// which are using it.
@inline(never)
public func getInt(_ x: Int) -> Int { x }
// The same for String.
@inline(never)
public func getString(_ s: String) -> String { s }
// The same for Substring.
@inline(never)
public func getSubstring(_ s: Substring) -> Substring { s }

View File

@ -0,0 +1,722 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===--- DriverUtils.swift ------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import Glibc
#else
import Darwin
// import LibProc
#endif
public struct BenchResults {
typealias T = Int
private let samples: [T]
let maxRSS: Int?
let stats: Stats
init(_ samples: [T], maxRSS: Int?) {
self.samples = samples.sorted()
self.maxRSS = maxRSS
self.stats = self.samples.reduce(into: Stats(), Stats.collect)
}
/// Return measured value for given `quantile`.
///
/// Equivalent to quantile estimate type R-1, SAS-3. See:
/// https://en.wikipedia.org/wiki/Quantile#Estimating_quantiles_from_a_sample
subscript(_ quantile: Double) -> T {
let index = Swift.max(
0,
Int((Double(self.samples.count) * quantile).rounded(.up)) - 1
)
return self.samples[index]
}
var sampleCount: T { self.samples.count }
var min: T { self.samples.first! }
var max: T { self.samples.last! }
var mean: T { Int(self.stats.mean.rounded()) }
var sd: T { Int(self.stats.standardDeviation.rounded()) }
var median: T { self[0.5] }
}
public var registeredBenchmarks: [BenchmarkInfo] = []
enum TestAction {
case run
case listTests
}
struct TestConfig {
/// The delimiter to use when printing output.
let delim: String
/// Duration of the test measurement in seconds.
///
/// Used to compute the number of iterations, if no fixed amount is specified.
/// This is useful when one wishes for a test to run for a
/// longer amount of time to perform performance analysis on the test in
/// instruments.
let sampleTime: Double
/// Number of iterations averaged in the sample.
/// When not specified, we'll compute the number of iterations to be averaged
/// in the sample from the actual runtime and the desired `sampleTime`.
let numIters: Int?
/// The number of samples we should take of each test.
let numSamples: Int?
/// Quantiles to report in results.
let quantile: Int?
/// Time unit in which to report results (nanos, micros, millis) (default: nanoseconds)
let timeUnit: TimeUnit
/// Report quantiles with delta encoding.
let delta: Bool
/// Is verbose output enabled?
let verbose: Bool
// Should we log the test's memory usage?
let logMemory: Bool
/// After we run the tests, should the harness sleep to allow for utilities
/// like leaks that require a PID to run on the test harness.
let afterRunSleep: UInt32?
/// The list of tests to run.
let tests: [(index: String, info: BenchmarkInfo)]
let action: TestAction
init(_ registeredBenchmarks: [BenchmarkInfo]) {
struct PartialTestConfig {
var delim: String?
var tags, skipTags: Set<BenchmarkCategory>?
var numSamples: UInt?
var numIters: UInt?
var quantile: UInt?
var timeUnit: String?
var delta: Bool?
var afterRunSleep: UInt32?
var sampleTime: Double?
var verbose: Bool?
var logMemory: Bool?
var action: TestAction?
var tests: [String]?
}
// Custom value type parsers
func tags(tags: String) throws -> Set<BenchmarkCategory> {
// We support specifying multiple tags by splitting on comma, i.e.:
// --tags=Array,Dictionary
// --skip-tags=Array,Set,unstable,skip
Set(
try tags.split(separator: ",").map(String.init).map {
try checked({ BenchmarkCategory(rawValue: $0) }, $0)
}
)
}
func finiteDouble(value: String) -> Double? {
Double(value).flatMap { $0.isFinite ? $0 : nil }
}
// Configure the command line argument parser
let p = ArgumentParser(into: PartialTestConfig())
p.addArgument(
"--num-samples", \.numSamples,
help: "number of samples to take per benchmark;\n" +
"default: 1 or auto-scaled to measure for\n" +
"`sample-time` if num-iters is also specified\n",
parser: { UInt($0) }
)
p.addArgument(
"--num-iters", \.numIters,
help: "number of iterations averaged in the sample;\n" +
"default: auto-scaled to measure for `sample-time`",
parser: { UInt($0) }
)
p.addArgument(
"--quantile", \.quantile,
help: "report quantiles instead of normal dist. stats;\n" +
"use 4 to get a five-number summary with quartiles,\n" +
"10 (deciles), 20 (ventiles), 100 (percentiles), etc.",
parser: { UInt($0) }
)
p.addArgument(
"--time-unit", \.timeUnit,
help: "time unit to be used for reported measurements;\n" +
"supported values: ns, us, ms; default: ns",
parser: { $0 }
)
p.addArgument(
"--delta", \.delta, defaultValue: true,
help: "report quantiles with delta encoding"
)
p.addArgument(
"--sample-time", \.sampleTime,
help: "duration of test measurement in seconds\ndefault: 1",
parser: finiteDouble
)
p.addArgument(
"--verbose", \.verbose, defaultValue: true,
help: "increase output verbosity"
)
p.addArgument(
"--memory", \.logMemory, defaultValue: true,
help: "log the change in maximum resident set size (MAX_RSS)"
)
p.addArgument(
"--delim", \.delim,
help: "value delimiter used for log output; default: ,",
parser: { $0 }
)
p.addArgument(
"--tags", \PartialTestConfig.tags,
help: "run tests matching all the specified categories",
parser: tags
)
p.addArgument(
"--skip-tags", \PartialTestConfig.skipTags, defaultValue: [],
help: "don't run tests matching any of the specified\n" +
"categories; default: unstable,skip",
parser: tags
)
p.addArgument(
"--sleep", \.afterRunSleep,
help: "number of seconds to sleep after benchmarking",
parser: { UInt32($0) }
)
p.addArgument(
"--list", \.action, defaultValue: .listTests,
help: "don't run the tests, just log the list of test \n" +
"numbers, names and tags (respects specified filters)"
)
p.addArgument(nil, \.tests) // positional arguments
let c = p.parse()
// Configure from the command line arguments, filling in the defaults.
self.delim = c.delim ?? ","
self.sampleTime = c.sampleTime ?? 1.0
self.numIters = c.numIters.map { Int($0) }
self.numSamples = c.numSamples.map { Int($0) }
self.quantile = c.quantile.map { Int($0) }
self.timeUnit = c.timeUnit.map { TimeUnit($0) } ?? TimeUnit.nanoseconds
self.delta = c.delta ?? false
self.verbose = c.verbose ?? false
self.logMemory = c.logMemory ?? false
self.afterRunSleep = c.afterRunSleep
self.action = c.action ?? .run
self.tests = TestConfig.filterTests(
registeredBenchmarks,
specifiedTests: Set(c.tests ?? []),
tags: c.tags ?? [],
skipTags: c.skipTags ?? [.unstable, .skip]
)
if self.logMemory, self.tests.count > 1 {
print(
"""
warning: The memory usage of a test, reported as the change in MAX_RSS,
is based on measuring the peak memory used by the whole process.
These results are meaningful only when running a single test,
not in the batch mode!
""")
}
// We always prepare the configuration string and call the print to have
// the same memory usage baseline between verbose and normal mode.
let testList = self.tests.map { $0.1.name }.joined(separator: ", ")
let configuration = """
--- CONFIG ---
NumSamples: \(numSamples ?? 0)
Verbose: \(verbose)
LogMemory: \(logMemory)
SampleTime: \(sampleTime)
NumIters: \(numIters ?? 0)
Quantile: \(quantile ?? 0)
TimeUnit: \(timeUnit)
Delimiter: \(String(reflecting: delim))
Tests Filter: \(c.tests ?? [])
Tests to run: \(testList)
--- DATA ---\n
"""
print(self.verbose ? configuration : "", terminator: "")
}
/// Returns the list of tests to run.
///
/// - Parameters:
/// - registeredBenchmarks: List of all performance tests to be filtered.
/// - specifiedTests: List of explicitly specified tests to run. These can
/// be specified either by a test name or a test number.
/// - tags: Run tests tagged with all of these categories.
/// - skipTags: Don't run tests tagged with any of these categories.
/// - Returns: An array of test number and benchmark info tuples satisfying
/// specified filtering conditions.
static func filterTests(
_ registeredBenchmarks: [BenchmarkInfo],
specifiedTests: Set<String>,
tags: Set<BenchmarkCategory>,
skipTags: Set<BenchmarkCategory>
) -> [(index: String, info: BenchmarkInfo)] {
let allTests = registeredBenchmarks.sorted()
let indices = Dictionary(
uniqueKeysWithValues:
zip(
allTests.map { $0.name },
(1...).lazy.map { String($0) }
)
)
func byTags(b: BenchmarkInfo) -> Bool {
b.tags.isSuperset(of: tags) &&
b.tags.isDisjoint(with: skipTags)
}
func byNamesOrIndices(b: BenchmarkInfo) -> Bool {
specifiedTests.contains(b.name) ||
specifiedTests.contains(indices[b.name]!)
} // !! "`allTests` have been assigned an index"
return allTests
.filter(specifiedTests.isEmpty ? byTags : byNamesOrIndices)
.map { (index: indices[$0.name]!, info: $0) }
}
}
struct Stats {
var n: Int = 0
var S: Double = 0.0
var mean: Double = 0.0
var variance: Double { self.n < 2 ? 0.0 : self.S / Double(self.n - 1) }
var standardDeviation: Double { self.variance.squareRoot() }
static func collect(_ s: inout Stats, _ x: Int) {
Stats.runningMeanVariance(&s, Double(x))
}
/// Compute running mean and variance using B. P. Welford's method.
///
/// See Knuth TAOCP vol 2, 3rd edition, page 232, or
/// https://www.johndcook.com/blog/standard_deviation/
static func runningMeanVariance(_ s: inout Stats, _ x: Double) {
let n = s.n + 1
let (k, M_, S_) = (Double(n), s.mean, s.S)
let M = M_ + (x - M_) / k
let S = S_ + (x - M_) * (x - M)
(s.n, s.mean, s.S) = (n, M, S)
}
}
#if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER
@_silgen_name("_swift_leaks_startTrackingObjects")
func startTrackingObjects(_: UnsafePointer<CChar>) -> Void
@_silgen_name("_swift_leaks_stopTrackingObjects")
func stopTrackingObjects(_: UnsafePointer<CChar>) -> Int
#endif
public final class Timer {
#if os(Linux)
public typealias TimeT = timespec
public init() {}
public func getTime() -> TimeT {
var ts = timespec(tv_sec: 0, tv_nsec: 0)
clock_gettime(CLOCK_REALTIME, &ts)
return ts
}
public func getTimeAsInt() -> UInt64 {
UInt64(getTime().tv_nsec)
}
public func diffTimeInNanoSeconds(from start: TimeT, to end: TimeT) -> UInt64 {
let oneSecond = 1_000_000_000 // ns
var elapsed = timespec(tv_sec: 0, tv_nsec: 0)
if end.tv_nsec - start.tv_nsec < 0 {
elapsed.tv_sec = end.tv_sec - start.tv_sec - 1
elapsed.tv_nsec = end.tv_nsec - start.tv_nsec + oneSecond
} else {
elapsed.tv_sec = end.tv_sec - start.tv_sec
elapsed.tv_nsec = end.tv_nsec - start.tv_nsec
}
return UInt64(elapsed.tv_sec) * UInt64(oneSecond) + UInt64(elapsed.tv_nsec)
}
#else
public typealias TimeT = UInt64
var info = mach_timebase_info_data_t(numer: 0, denom: 0)
public init() {
mach_timebase_info(&info)
}
public func getTime() -> TimeT {
mach_absolute_time()
}
public func getTimeAsInt() -> UInt64 {
UInt64(getTime())
}
public func diffTimeInNanoSeconds(from start: TimeT, to end: TimeT) -> UInt64 {
let elapsed = end - start
return elapsed * UInt64(info.numer) / UInt64(info.denom)
}
#endif
}
extension UInt64 {
public var nanoseconds: Int { Int(self) }
public var microseconds: Int { Int(self / 1000) }
public var milliseconds: Int { Int(self / 1000 / 1000) }
public var seconds: Int { Int(self / 1000 / 1000 / 1000) }
}
enum TimeUnit: String {
case nanoseconds = "ns"
case microseconds = "μs"
case milliseconds = "ms"
case seconds = "s"
init(_ from: String) {
switch from {
case "ns": self = .nanoseconds
case "us", "μs": self = .microseconds
case "ms": self = .milliseconds
case "s": self = .seconds
default: fatalError("Only the following time units are supported: ns, us, ms, s")
}
}
static var `default` = TimeUnit.nanoseconds
}
extension TimeUnit: CustomStringConvertible {
public var description: String {
self.rawValue
}
}
/// Performance test runner that measures benchmarks and reports the results.
final class TestRunner {
let c: TestConfig
let timer = Timer()
var start, end, lastYield: Timer.TimeT
let baseline = TestRunner.getResourceUtilization()
let schedulerQuantum = UInt64(10_000_000) // nanoseconds (== 10ms, macos)
init(_ config: TestConfig) {
self.c = config
let now = timer.getTime()
(start, end, lastYield) = (now, now, now)
}
/// Offer to yield CPU to other processes and return current time on resume.
func yield() -> Timer.TimeT {
sched_yield()
return timer.getTime()
}
#if os(Linux)
private static func getExecutedInstructions() -> UInt64 {
// FIXME: there is a Linux PMC API you can use to get this, but it's
// not quite so straightforward.
0
}
#else
private static func getExecutedInstructions() -> UInt64 {
// if #available(OSX 10.9, iOS 7.0, *) {
// var u = rusage_info_v4()
// let p = UnsafeMutablePointer(&u)
// p.withMemoryRebound(to: Optional<rusage_info_t>.self, capacity: 1) { up in
// let _ = proc_pid_rusage(getpid(), RUSAGE_INFO_V4, up)
// }
// return u.ri_instructions
// } else {
0
// }
}
#endif
private static func getResourceUtilization() -> rusage {
#if canImport(Darwin)
let rusageSelf = RUSAGE_SELF
#else
let rusageSelf = RUSAGE_SELF.rawValue
#endif
var u = rusage(); getrusage(rusageSelf, &u); return u
}
/// Returns maximum resident set size (MAX_RSS) delta in bytes.
///
/// This method of estimating memory usage is valid only for executing single
/// benchmark. That's why we don't worry about reseting the `baseline` in
/// `resetMeasurements`.
///
// FIXME: This current implementation doesn't work on Linux. It is disabled
/// permanently to avoid linker errors. Feel free to fix.
func measureMemoryUsage() -> Int? {
#if os(Linux)
return nil
#else
guard c.logMemory else { return nil }
let current = TestRunner.getResourceUtilization()
let maxRSS = current.ru_maxrss - baseline.ru_maxrss
#if canImport(Darwin)
let pageSize = _SC_PAGESIZE
#else
let pageSize = Int32(_SC_PAGESIZE)
#endif
let pages = { maxRSS / sysconf(pageSize) }
func deltaEquation(_ stat: KeyPath<rusage, Int>) -> String {
let b = baseline[keyPath: stat], c = current[keyPath: stat]
return "\(c) - \(b) = \(c - b)"
}
logVerbose(
"""
MAX_RSS \(deltaEquation(\rusage.ru_maxrss)) (\(pages()) pages)
ICS \(deltaEquation(\rusage.ru_nivcsw))
VCS \(deltaEquation(\rusage.ru_nvcsw))
""")
return maxRSS
#endif
}
private func startMeasurement() {
let spent = timer.diffTimeInNanoSeconds(from: lastYield, to: end)
let nextSampleEstimate = UInt64(Double(lastSampleTime) * 1.5)
if spent + nextSampleEstimate < schedulerQuantum {
start = timer.getTime()
} else {
logVerbose(" Yielding after ~\(spent.nanoseconds) ns")
let now = yield()
(start, lastYield) = (now, now)
}
}
private func stopMeasurement() {
end = timer.getTime()
}
private func resetMeasurements() {
let now = yield()
(start, end, lastYield) = (now, now, now)
}
/// Time in nanoseconds spent running the last function
var lastSampleTime: UInt64 {
timer.diffTimeInNanoSeconds(from: start, to: end)
}
/// Measure the `fn` and return the average sample time per iteration (in c.timeUnit).
func measure(_ name: String, fn: (Int) -> Void, numIters: Int) -> Int {
#if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER
name.withCString { p in startTrackingObjects(p) }
#endif
startMeasurement()
fn(numIters)
stopMeasurement()
#if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER
name.withCString { p in stopTrackingObjects(p) }
#endif
switch c.timeUnit {
case .nanoseconds: return lastSampleTime.nanoseconds / numIters
case .microseconds: return lastSampleTime.microseconds / numIters
case .milliseconds: return lastSampleTime.milliseconds / numIters
case .seconds: return lastSampleTime.seconds / numIters
}
}
func logVerbose(_ msg: @autoclosure () -> String) {
if c.verbose { print(msg()) }
}
/// Run the benchmark and return the measured results.
func run(_ test: BenchmarkInfo) -> BenchResults? {
// Before we do anything, check that we actually have a function to
// run. If we don't it is because the benchmark is not supported on
// the platform and we should skip it.
guard let testFn = test.runFunction else {
logVerbose("Skipping unsupported benchmark \(test.name)!")
return nil
}
logVerbose("Running \(test.name)")
var samples: [Int] = []
func addSample(_ time: Int) {
logVerbose(" Sample \(samples.count),\(time)")
samples.append(time)
}
resetMeasurements()
if let setUp = test.setUpFunction {
setUp()
stopMeasurement()
logVerbose(" SetUp \(lastSampleTime.microseconds)")
resetMeasurements()
}
// Determine number of iterations for testFn to run for desired time.
func iterationsPerSampleTime() -> (numIters: Int, oneIter: Int) {
let oneIter = measure(test.name, fn: testFn, numIters: 1)
if oneIter > 0 {
let timePerSample = Int(c.sampleTime * 1_000_000.0) // microseconds (μs)
return (max(timePerSample / oneIter, 1), oneIter)
} else {
return (1, oneIter)
}
}
// Determine the scale of measurements. Re-use the calibration result if
// it is just one measurement.
func calibrateMeasurements() -> Int {
let (numIters, oneIter) = iterationsPerSampleTime()
if numIters == 1 { addSample(oneIter) }
else { resetMeasurements() } // for accurate yielding reports
return numIters
}
let numIters = min( // Cap to prevent overflow on 32-bit systems when scaled
Int.max / 10000, // by the inner loop multiplier inside the `testFn`.
c.numIters ?? calibrateMeasurements()
)
let numSamples = c.numSamples ?? min(
200, // Cap the number of samples
c.numIters == nil ? 1 : calibrateMeasurements()
)
samples.reserveCapacity(numSamples)
logVerbose(" Collecting \(numSamples) samples.")
logVerbose(" Measuring with scale \(numIters).")
for _ in samples.count ..< numSamples {
addSample(measure(test.name, fn: testFn, numIters: numIters))
}
test.tearDownFunction?()
if let lf = test.legacyFactor {
logVerbose(" Applying legacy factor: \(lf)")
samples = samples.map { $0 * lf }
}
return BenchResults(samples, maxRSS: measureMemoryUsage())
}
var header: String {
let withUnit = { $0 + "(\(self.c.timeUnit))" }
let withDelta = { "𝚫" + $0 }
func quantiles(q: Int) -> [String] {
// See https://en.wikipedia.org/wiki/Quantile#Specialized_quantiles
let prefix = [
2: "MEDIAN", 3: "T", 4: "Q", 5: "QU", 6: "S", 7: "O", 10: "D",
12: "Dd", 16: "H", 20: "V", 33: "TT", 100: "P", 1000: "Pr",
][q, default: "\(q)-q"]
let base20 = "0123456789ABCDEFGHIJ".map { String($0) }
let index: (Int) -> String =
{ q == 2 ? "" : q <= 20 ? base20[$0] : String($0) }
let tail = (1 ..< q).map { prefix + index($0) } + ["MAX"]
return [withUnit("MIN")] + tail.map(c.delta ? withDelta : withUnit)
}
return (
["#", "TEST", "SAMPLES"] +
(
c.quantile.map(quantiles)
?? ["MIN", "MAX", "MEAN", "SD", "MEDIAN"].map(withUnit)
) +
(c.logMemory ? ["MAX_RSS(B)"] : [])
).joined(separator: c.delim)
}
/// Execute benchmarks and continuously report the measurement results.
func runBenchmarks() {
var testCount = 0
func report(_ index: String, _ t: BenchmarkInfo, results: BenchResults?) {
func values(r: BenchResults) -> [String] {
func quantiles(q: Int) -> [Int] {
let qs = (0 ... q).map { i in r[Double(i) / Double(q)] }
return c.delta ?
qs.reduce(into: (encoded: [], last: 0)) {
$0.encoded.append($1 - $0.last); $0.last = $1
}.encoded : qs
}
return (
[r.sampleCount] +
(
c.quantile.map(quantiles)
?? [r.min, r.max, r.mean, r.sd, r.median]
) +
[r.maxRSS].compactMap { $0 }
).map { (c.delta && $0 == 0) ? "" : String($0) } // drop 0s in deltas
}
let benchmarkStats = (
[index, t.name] + (results.map(values) ?? ["Unsupported"])
).joined(separator: c.delim)
print(benchmarkStats)
fflush(stdout)
if results != nil {
testCount += 1
}
}
print(header)
for (index, test) in c.tests {
report(index, test, results: run(test))
}
print("\nTotal performance tests executed: \(testCount)")
}
}
public func main() {
let config = TestConfig(registeredBenchmarks)
switch config.action {
case .listTests:
print("#\(config.delim)Test\(config.delim)[Tags]")
for (index, t) in config.tests {
let testDescription = [index, t.name, t.tags.sorted().description]
.joined(separator: config.delim)
print(testDescription)
}
case .run:
TestRunner(config).runBenchmarks()
if let x = config.afterRunSleep {
sleep(x)
}
}
}

View File

@ -0,0 +1,6 @@
## Swift Benchmark Utils
> This benchmarking infrastructure is copied from
https://github.com/apple/swift/tree/cf53143a47278c2a465409a67376642515956777/benchmark/utils
with the intent of producing similar look and feel, as well as because we need some benchmark infra.
> When feasible we will aim to collaborate and contribute improvements back to the mainline Swift project.