Skip to content

Commit b2a49f9

Browse files
committed
Add parse time measurement + cleanup benchmarker
1 parent 9062855 commit b2a49f9

File tree

8 files changed

+141
-117
lines changed

8 files changed

+141
-117
lines changed

Sources/RegexBenchmark/Benchmark.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ protocol RegexBenchmark {
99

1010
protocol SwiftRegexBenchmark: RegexBenchmark {
1111
var regex: Regex<AnyRegexOutput> { get set }
12+
var pattern: String? { get }
1213
mutating func compile()
14+
mutating func parse() -> Bool
1315
mutating func enableTracing()
1416
mutating func enableMetrics()
1517
}
@@ -18,6 +20,14 @@ extension SwiftRegexBenchmark {
1820
mutating func compile() {
1921
let _ = regex._forceAction(.recompile)
2022
}
23+
mutating func parse() -> Bool {
24+
if let s = pattern {
25+
let _ = regex._forceAction(.parse(s))
26+
return true
27+
} else {
28+
return false
29+
}
30+
}
2131
mutating func enableTracing() {
2232
let _ = regex._forceAction(.addOptions(.enableTracing))
2333
}
@@ -29,6 +39,7 @@ extension SwiftRegexBenchmark {
2939
struct Benchmark: SwiftRegexBenchmark {
3040
let name: String
3141
var regex: Regex<AnyRegexOutput>
42+
let pattern: String?
3243
let type: MatchType
3344
let target: String
3445

@@ -74,6 +85,7 @@ struct NSBenchmark: RegexBenchmark {
7485
struct InputListBenchmark: SwiftRegexBenchmark {
7586
let name: String
7687
var regex: Regex<AnyRegexOutput>
88+
let pattern: String?
7789
let targets: [String]
7890

7991
func run() {
@@ -145,6 +157,7 @@ struct CrossBenchmark {
145157
Benchmark(
146158
name: baseName + "Whole",
147159
regex: swiftRegex,
160+
pattern: regex,
148161
type: .whole,
149162
target: input))
150163
runner.register(
@@ -158,6 +171,7 @@ struct CrossBenchmark {
158171
Benchmark(
159172
name: baseName + "All",
160173
regex: swiftRegex,
174+
pattern: regex,
161175
type: .allMatches,
162176
target: input))
163177
runner.register(
@@ -171,6 +185,7 @@ struct CrossBenchmark {
171185
Benchmark(
172186
name: baseName + "First",
173187
regex: swiftRegex,
188+
pattern: regex,
174189
type: .first,
175190
target: input))
176191
runner.register(
@@ -200,6 +215,7 @@ struct CrossInputListBenchmark {
200215
runner.register(InputListBenchmark(
201216
name: baseName,
202217
regex: swiftRegex,
218+
pattern: regex,
203219
targets: inputs
204220
))
205221
runner.register(InputListNSBenchmark(

Sources/RegexBenchmark/BenchmarkChart.swift

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@ import SwiftUI
1717
struct BenchmarkChart: View {
1818
var comparisons: [BenchmarkResult.Comparison]
1919

20+
// Sort by normalized difference
2021
var sortedComparisons: [BenchmarkResult.Comparison] {
2122
comparisons.sorted { a, b in
22-
a.latest.median.seconds/a.baseline.median.seconds <
23-
b.latest.median.seconds/b.baseline.median.seconds
23+
a.normalizedDiff < b.normalizedDiff
2424
}
2525
}
2626
var body: some View {
2727
VStack(alignment: .leading) {
2828
Chart {
2929
ForEach(sortedComparisons) { comparison in
30-
let new = comparison.latest.median.seconds
31-
let old = comparison.baseline.median.seconds
32-
chartBody(
33-
name: comparison.name,
34-
new: new,
35-
old: old,
36-
sampleCount: comparison.latest.samples)
30+
// Normalized runtime
31+
BarMark(
32+
x: .value("Name", comparison.name),
33+
y: .value("Normalized runtime", comparison.normalizedDiff))
34+
.foregroundStyle(LinearGradient(
35+
colors: [.accentColor, comparison.diff?.seconds ?? 0 <= 0 ? .green : .yellow],
36+
startPoint: .bottom,
37+
endPoint: .top))
3738
}
3839
// Baseline
3940
RuleMark(y: .value("Time", 1.0))
@@ -43,23 +44,6 @@ struct BenchmarkChart: View {
4344
}.frame(idealHeight: 400)
4445
}
4546
}
46-
47-
@ChartContentBuilder
48-
func chartBody(
49-
name: String,
50-
new: TimeInterval,
51-
old: TimeInterval,
52-
sampleCount: Int
53-
) -> some ChartContent {
54-
// Normalized runtime
55-
BarMark(
56-
x: .value("Name", name),
57-
y: .value("Normalized runtime", new / old))
58-
.foregroundStyle(LinearGradient(
59-
colors: [.accentColor, new - old <= 0 ? .green : .yellow],
60-
startPoint: .bottom,
61-
endPoint: .top))
62-
}
6347
}
6448

6549
struct BenchmarkResultApp: App {

Sources/RegexBenchmark/BenchmarkResults.swift

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,7 @@ extension BenchmarkRunner {
106106
=== Comparison chart =================================================================
107107
Press Control-C to close...
108108
""")
109-
BenchmarkResultApp.comparisons = {
110-
return comparisons.sorted {
111-
let delta0 = Float($0.latest.median.seconds - $0.baseline.median.seconds)
112-
/ Float($0.baseline.median.seconds)
113-
let delta1 = Float($1.latest.median.seconds - $1.baseline.median.seconds)
114-
/ Float($1.baseline.median.seconds)
115-
return delta0 > delta1
116-
}
117-
}()
109+
BenchmarkResultApp.comparisons = comparisons
118110
BenchmarkResultApp.main()
119111
}
120112
#endif
@@ -141,11 +133,43 @@ extension BenchmarkRunner {
141133
}
142134
}
143135

144-
struct BenchmarkResult: Codable {
145-
let compileTime: Time
136+
struct Measurement: Codable, CustomStringConvertible {
146137
let median: Time
147138
let stdev: Double
148139
let samples: Int
140+
141+
init(results: [Time]) {
142+
let sorted = results.sorted()
143+
self.samples = sorted.count
144+
self.median = sorted[samples/2]
145+
let sum = results.reduce(0.0) {acc, next in acc + next.seconds}
146+
let mean = sum / Double(samples)
147+
let squareDiffs = results.reduce(0.0) { acc, next in
148+
acc + pow(next.seconds - mean, 2)
149+
}
150+
self.stdev = (squareDiffs / Double(samples)).squareRoot()
151+
}
152+
153+
var description: String {
154+
return "\(median) (stdev: \(Time(stdev)), N = \(samples))"
155+
}
156+
}
157+
158+
struct BenchmarkResult: Codable, CustomStringConvertible {
159+
let runtime: Measurement
160+
let compileTime: Measurement?
161+
let parseTime: Measurement?
162+
163+
var description: String {
164+
var base = "> run time: \(runtime.description)"
165+
if let compileTime = compileTime {
166+
base += "\n> compile time: \(compileTime)"
167+
}
168+
if let parseTime = parseTime {
169+
base += "\n> parse time: \(parseTime)"
170+
}
171+
return base
172+
}
149173
}
150174

151175
extension BenchmarkResult {
@@ -160,56 +184,57 @@ extension BenchmarkResult {
160184
case runtime
161185
case compileTime
162186
}
187+
188+
var latestTime: Time {
189+
switch type {
190+
case .compileTime:
191+
return latest.compileTime?.median ?? .zero
192+
case .runtime:
193+
return latest.runtime.median
194+
}
195+
}
196+
197+
var baselineTime: Time {
198+
switch type {
199+
case .compileTime:
200+
return baseline.compileTime?.median ?? .zero
201+
case .runtime:
202+
return baseline.runtime.median
203+
}
204+
}
163205

164206
var diff: Time? {
165207
switch type {
166208
case .compileTime:
167-
return latest.compileTime - baseline.compileTime
209+
return latestTime - baselineTime
168210
case .runtime:
169-
if Stats.tTest(baseline, latest) {
170-
return latest.median - baseline.median
211+
if Stats.tTest(baseline.runtime, latest.runtime) {
212+
return latestTime - baselineTime
171213
}
172214
return nil
173215
}
174-
216+
}
217+
218+
var normalizedDiff: Double {
219+
latestTime.seconds/baselineTime.seconds
175220
}
176221

177222
var description: String {
178223
guard let diff = diff else {
179224
return "- \(name) N/A"
180225
}
181-
let oldVal: Time
182-
let newVal: Time
183-
switch type {
184-
case .compileTime:
185-
oldVal = baseline.compileTime
186-
newVal = latest.compileTime
187-
case .runtime:
188-
oldVal = baseline.median
189-
newVal = latest.median
190-
}
191-
let percentage = (1000 * diff.seconds / oldVal.seconds).rounded()/10
226+
let percentage = (1000 * diff.seconds / baselineTime.seconds).rounded()/10
192227
let len = max(40 - name.count, 1)
193228
let nameSpacing = String(repeating: " ", count: len)
194-
return "- \(name)\(nameSpacing)\(newVal)\t\(oldVal)\t\(diff)\t\t\(percentage)%"
229+
return "- \(name)\(nameSpacing)\(latestTime)\t\(baselineTime)\t\(diff)\t\t\(percentage)%"
195230
}
196231

197232
var asCsv: String {
198233
guard let diff = diff else {
199234
return "\(name),N/A"
200235
}
201-
let oldVal: Time
202-
let newVal: Time
203-
switch type {
204-
case .compileTime:
205-
oldVal = baseline.compileTime
206-
newVal = latest.compileTime
207-
case .runtime:
208-
oldVal = baseline.median
209-
newVal = latest.median
210-
}
211-
let percentage = (1000 * diff.seconds / oldVal.seconds).rounded()/10
212-
return "\"\(name)\",\(newVal.seconds),\(oldVal.seconds),\(diff.seconds),\(percentage)%"
236+
let percentage = (1000 * diff.seconds / baselineTime.seconds).rounded()/10
237+
return "\"\(name)\",\(latestTime.seconds),\(baselineTime.seconds),\(diff.seconds),\(percentage)%"
213238
}
214239
}
215240
}

0 commit comments

Comments
 (0)