Skip to content

Commit

Permalink
feat(minor): Add Benchmark parameters
Browse files Browse the repository at this point in the history
Sometimes it is convenient to run the same benchmark multiple times under slightly different scenarios.
For example, benchmarking `func foo(_ n: Int)` for various parameterizations of `n`.
This adds support for specifying the parameters of a benchmark but makes no functional changes beyond the name/identifier of a benchmark now including a parameter list when one was provided.

This unlocks the potential for the benchmark exporters (such as the Influx exporter) to leverage these parameters (by emitting appropriate tags/fields in the case of Influx) in their output.
  • Loading branch information
Jairon Terrero committed Sep 16, 2024
1 parent 75e8622 commit c957cba
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 4 deletions.
34 changes: 30 additions & 4 deletions Sources/Benchmark/Benchmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,28 @@ public final class Benchmark: Codable, Hashable {
#endif
public static var benchmarks: [Benchmark] = [] // Bookkeeping of all registered benchmarks

/// The default name used for display purposes of the benchmark when no parameters exist.
private var baseName: String

/// The name used for display purposes of the benchmark (also used for matching when comparing to baselines)
public var name: String
public var name: String {
get {
if configuration.parameters.isEmpty {
baseName
} else {
baseName
+ " ("
+ configuration.parameters
.sorted(by: { $0.name < $1.name })
.map({ "\($0.name): \($0.value)" })
.joined(separator: ", ")
+ ")"
}
}
set {
baseName = newValue

Check warning on line 91 in Sources/Benchmark/Benchmark.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Benchmark/Benchmark.swift#L90-L91

Added lines #L90 - L91 were not covered by tests
}
}

/// The reason for a benchmark failure, not set if successful
public var failureReason: String?
Expand Down Expand Up @@ -119,6 +139,7 @@ public final class Benchmark: Codable, Hashable {

/// Hook for setting defaults for a whole benchmark suite
public static var defaultConfiguration: Configuration = .init(metrics: BenchmarkMetric.default,
parameters: [],
timeUnits: .automatic,
warmupIterations: 1,
scalingFactor: .one,
Expand All @@ -131,7 +152,7 @@ public final class Benchmark: Codable, Hashable {
var measurementCompleted = false // Keep track so we skip multiple 'end of measurement'

enum CodingKeys: String, CodingKey {
case name
case baseName = "name"
case target
case executablePath
case configuration
Expand Down Expand Up @@ -168,7 +189,7 @@ public final class Benchmark: Codable, Hashable {
return nil
}
target = ""
self.name = name
self.baseName = name
self.configuration = configuration
self.closure = closure
self.setup = setup
Expand All @@ -193,7 +214,7 @@ public final class Benchmark: Codable, Hashable {
return nil
}
target = ""
self.name = name
self.baseName = name
self.configuration = configuration
asyncClosure = closure
self.setup = setup
Expand Down Expand Up @@ -362,6 +383,8 @@ public extension Benchmark {
struct Configuration: Codable {
/// Defines the metrics that should be measured for the benchmark
public var metrics: [BenchmarkMetric]
/// Specifies the parameters used to define the benchmark.
public var parameters: [BenchmarkParameter]
/// Override the automatic detection of timeunits for metrics related to time to a specific
/// one (auto should work for most use cases)
public var timeUnits: BenchmarkTimeUnits
Expand All @@ -386,6 +409,7 @@ public extension Benchmark {
public var teardown: BenchmarkTeardownHook?

public init(metrics: [BenchmarkMetric] = defaultConfiguration.metrics,
parameters: [BenchmarkParameter] = defaultConfiguration.parameters,
timeUnits: BenchmarkTimeUnits = defaultConfiguration.timeUnits,
warmupIterations: Int = defaultConfiguration.warmupIterations,
scalingFactor: BenchmarkScalingFactor = defaultConfiguration.scalingFactor,
Expand All @@ -397,6 +421,7 @@ public extension Benchmark {
setup: BenchmarkSetupHook? = nil,
teardown: BenchmarkTeardownHook? = nil) {
self.metrics = metrics
self.parameters = parameters
self.timeUnits = timeUnits
self.warmupIterations = warmupIterations
self.scalingFactor = scalingFactor
Expand All @@ -411,6 +436,7 @@ public extension Benchmark {
// swiftlint:disable nesting
enum CodingKeys: String, CodingKey {
case metrics
case parameters
case timeUnits
case warmupIterations
case scalingFactor
Expand Down
58 changes: 58 additions & 0 deletions Sources/Benchmark/BenchmarkParameter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

Check failure on line 1 in Sources/Benchmark/BenchmarkParameter.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Leading Whitespace Violation: Files should not contain leading whitespace (leading_whitespace)
/// Represents a parameter used to define the implementation of a benchmark.
/// Useful for running the same benchmark under a set of unique scenarios.
///
/// For example, running multiple benchmarks of `func foo(_ n:Int)`
/// for various parameterizations of `n`.
public struct BenchmarkParameter: Codable {
public let name: String

/// Describes the range of possible values for this parameter.
public let range: Range
public enum Range: Codable { case finite, infinite }

public let value: Value

/// Represents a value that may be one of a set of allowed types.
public enum Value: Codable {
case string(String)
case int(Int)
case double(Double)
}

public init(_ name: String, _ value: Value, range: Range) {
self.name = name
self.value = value
self.range = range
}
}

extension BenchmarkParameter.Value: CustomStringConvertible {
public var description: String {
switch self {
case let .string(string):

Check failure on line 33 in Sources/Benchmark/BenchmarkParameter.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Switch and Case Statement Alignment Violation: Case statements should vertically aligned with their closing brace (switch_case_alignment)
string
case let .int(int):

Check failure on line 35 in Sources/Benchmark/BenchmarkParameter.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Switch and Case Statement Alignment Violation: Case statements should vertically aligned with their closing brace (switch_case_alignment)
int.description
case let .double(double):

Check failure on line 37 in Sources/Benchmark/BenchmarkParameter.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Switch and Case Statement Alignment Violation: Case statements should vertically aligned with their closing brace (switch_case_alignment)
double.description

Check warning on line 38 in Sources/Benchmark/BenchmarkParameter.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Benchmark/BenchmarkParameter.swift#L38

Added line #L38 was not covered by tests
}
}
}


Check failure on line 43 in Sources/Benchmark/BenchmarkParameter.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Vertical Whitespace Violation: Limit vertical whitespace to a single empty line; currently 2 (vertical_whitespace)
extension BenchmarkParameter.Value: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
self = .string(value)

Check warning on line 46 in Sources/Benchmark/BenchmarkParameter.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Benchmark/BenchmarkParameter.swift#L45-L46

Added lines #L45 - L46 were not covered by tests
}
}
extension BenchmarkParameter.Value: ExpressibleByIntegerLiteral {
public init(integerLiteral value: IntegerLiteralType) {
self = .int(value)

Check warning on line 51 in Sources/Benchmark/BenchmarkParameter.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Benchmark/BenchmarkParameter.swift#L50-L51

Added lines #L50 - L51 were not covered by tests
}
}
extension BenchmarkParameter.Value: ExpressibleByFloatLiteral {
public init(floatLiteral value: FloatLiteralType) {
self = .double(value)

Check warning on line 56 in Sources/Benchmark/BenchmarkParameter.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Benchmark/BenchmarkParameter.swift#L55-L56

Added lines #L55 - L56 were not covered by tests
}
}
12 changes: 12 additions & 0 deletions Tests/BenchmarkTests/BenchmarkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,16 @@ final class BenchmarkTests: XCTestCase {
XCTAssertNotNil(benchmark)
benchmark?.run()
}

func testBenchmarkParameterizedDescription() throws {
let benchmark = Benchmark("testBenchmarkParameterizedDescription benchmark",
configuration: .init(
parameters: [
BenchmarkParameter("foo", .string("bar"), range: .finite),
BenchmarkParameter("bin", .int(42), range: .infinite),

Check failure on line 82 in Tests/BenchmarkTests/BenchmarkTests.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
]
)) { _ in }
XCTAssertNotNil(benchmark)
XCTAssertEqual(benchmark?.name, "testBenchmarkParameterizedDescription benchmark (bin: 42, foo: bar)")
}
}

0 comments on commit c957cba

Please sign in to comment.