Skip to content

Commit 8dd3886

Browse files
committed
Merge branch 'main' into main-merge
2 parents 14eb23f + e87149a commit 8dd3886

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+7824
-2756
lines changed

Sources/RegexBenchmark/Benchmark.swift

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import Foundation
44
public protocol RegexBenchmark {
55
var name: String { get }
66
func run()
7+
func debug()
78
}
89

910
public struct Benchmark: RegexBenchmark {
1011
public let name: String
11-
let regex: Regex<Substring>
12-
let ty: MatchType
12+
let regex: Regex<AnyRegexOutput>
13+
let type: MatchType
1314
let target: String
1415

1516
public enum MatchType {
@@ -19,7 +20,7 @@ public struct Benchmark: RegexBenchmark {
1920
}
2021

2122
public func run() {
22-
switch ty {
23+
switch type {
2324
case .whole: blackHole(target.wholeMatch(of: regex))
2425
case .allMatches: blackHole(target.matches(of: regex))
2526
case .first: blackHole(target.firstMatch(of: regex))
@@ -30,86 +31,100 @@ public struct Benchmark: RegexBenchmark {
3031
public struct NSBenchmark: RegexBenchmark {
3132
public let name: String
3233
let regex: NSRegularExpression
33-
let ty: NSMatchType
34+
let type: NSMatchType
3435
let target: String
3536

3637
var range: NSRange {
3738
NSRange(target.startIndex..<target.endIndex, in: target)
3839
}
3940

4041
public enum NSMatchType {
41-
case all
42+
case allMatches
4243
case first
4344
}
4445

4546
public func run() {
46-
switch ty {
47-
case .all: blackHole(regex.matches(in: target, range: range))
47+
switch type {
48+
case .allMatches: blackHole(regex.matches(in: target, range: range))
4849
case .first: blackHole(regex.firstMatch(in: target, range: range))
4950
}
5051
}
5152
}
5253

53-
public struct BenchmarkRunner {
54-
// Register instances of Benchmark and run them
55-
let suiteName: String
56-
var suite: [any RegexBenchmark]
57-
let samples: Int
58-
59-
public init(_ suiteName: String) {
60-
self.suiteName = suiteName
61-
self.suite = []
62-
self.samples = 20
63-
}
64-
65-
public init(_ suiteName: String, _ n: Int) {
66-
self.suiteName = suiteName
67-
self.suite = []
68-
self.samples = n
69-
}
54+
/// A benchmark meant to be ran across multiple engines
55+
struct CrossBenchmark {
56+
/// The base name of the benchmark
57+
var baseName: String
7058

71-
public mutating func register(_ new: some RegexBenchmark) {
72-
suite.append(new)
73-
}
74-
75-
func measure(benchmark: some RegexBenchmark) -> Time {
76-
var times: [Time] = []
77-
78-
// initial run to make sure the regex has been compiled
79-
benchmark.run()
80-
81-
// fixme: use suspendingclock?
82-
for _ in 0..<samples {
83-
let start = Tick.now
84-
benchmark.run()
85-
let end = Tick.now
86-
let time = end.elapsedTime(since: start)
87-
times.append(time)
88-
}
89-
// todo: compute stdev and warn if it's too large
90-
91-
// return median time
92-
times.sort()
93-
return times[samples/2]
94-
}
95-
96-
public func run() {
97-
print("Running")
98-
for b in suite {
99-
print("- \(b.name) \(measure(benchmark: b))")
59+
/// The string to compile in differnet engines
60+
var regex: String
61+
62+
/// The text to search
63+
var input: String
64+
65+
// TODO: var output, for validation
66+
67+
/// Whether this is whole string matching or a searching benchmark
68+
///
69+
/// TODO: Probably better ot have a whole-line vs search anywhere, maybe
70+
/// accomodate multi-line matching, etc.
71+
var isWhole: Bool = false
72+
73+
func register(_ runner: inout BenchmarkRunner) {
74+
let swiftRegex = try! Regex(regex)
75+
76+
let nsPattern = isWhole ? "^" + regex + "$" : regex
77+
let nsRegex: NSRegularExpression
78+
if isWhole {
79+
nsRegex = try! NSRegularExpression(pattern: "^" + regex + "$")
80+
} else {
81+
nsRegex = try! NSRegularExpression(pattern: regex)
10082
}
101-
}
102-
103-
public func profile() {
104-
print("Starting")
105-
for b in suite {
106-
print("- \(b.name)")
107-
b.run()
108-
print("- done")
83+
84+
if isWhole {
85+
runner.register(
86+
Benchmark(
87+
name: baseName + "Whole",
88+
regex: swiftRegex,
89+
type: .whole,
90+
target: input))
91+
runner.register(
92+
NSBenchmark(
93+
name: baseName + "Whole_NS",
94+
regex: nsRegex,
95+
type: .first,
96+
target: input))
97+
} else {
98+
runner.register(
99+
Benchmark(
100+
name: baseName + "First",
101+
regex: swiftRegex,
102+
type: .first,
103+
target: input))
104+
runner.register(
105+
Benchmark(
106+
name: baseName + "All",
107+
regex: swiftRegex,
108+
type: .allMatches,
109+
target: input))
110+
runner.register(
111+
NSBenchmark(
112+
name: baseName + "First_NS",
113+
regex: nsRegex,
114+
type: .first,
115+
target: input))
116+
runner.register(
117+
NSBenchmark(
118+
name: baseName + "All_NS",
119+
regex: nsRegex,
120+
type: .allMatches,
121+
target: input))
109122
}
110123
}
111124
}
112125

126+
// TODO: Capture-containing benchmarks
127+
113128
// nom nom nom, consume the argument
114129
@inline(never)
115130
public func blackHole<T>(_ x: T) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This file has lines generated by createBenchmark.py
2+
// Do not remove the start of registration or end of registration markers
3+
4+
extension BenchmarkRunner {
5+
public static func makeRunner(
6+
_ samples: Int,
7+
_ outputPath: String
8+
) -> BenchmarkRunner {
9+
var benchmark = BenchmarkRunner("RegexBench", samples, outputPath)
10+
// -- start of registrations --
11+
benchmark.addReluctantQuant()
12+
benchmark.addCSS()
13+
benchmark.addNotFound()
14+
benchmark.addGraphemeBreak()
15+
benchmark.addHangulSyllable()
16+
benchmark.addHTML()
17+
benchmark.addEmail()
18+
benchmark.addCustomCharacterClasses()
19+
// -- end of registrations --
20+
return benchmark
21+
}
22+
}

0 commit comments

Comments
 (0)