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

Unicode and DNA regexes + more benchmarker improvements #515

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions Sources/RegexBenchmark/Benchmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ struct CrossBenchmark {
/// TODO: Probably better ot have a whole-line vs search anywhere, maybe
/// accomodate multi-line matching, etc.
var isWhole: Bool = false

/// Whether or not to do firstMatch as well or just allMatches
var includeFirst: Bool = false

func register(_ runner: inout BenchmarkRunner) {
let swiftRegex = try! Regex(regex)

let nsPattern = isWhole ? "^" + regex + "$" : regex
let nsRegex: NSRegularExpression
if isWhole {
nsRegex = try! NSRegularExpression(pattern: "^" + regex + "$")
Expand All @@ -95,30 +96,32 @@ struct CrossBenchmark {
type: .first,
target: input))
} else {
runner.register(
Benchmark(
name: baseName + "First",
regex: swiftRegex,
type: .first,
target: input))
runner.register(
Benchmark(
name: baseName + "All",
regex: swiftRegex,
type: .allMatches,
target: input))
runner.register(
NSBenchmark(
name: baseName + "First_NS",
regex: nsRegex,
type: .first,
target: input))
runner.register(
NSBenchmark(
name: baseName + "All_NS",
regex: nsRegex,
type: .allMatches,
target: input))
if includeFirst {
runner.register(
Benchmark(
name: baseName + "First",
regex: swiftRegex,
type: .first,
target: input))
runner.register(
NSBenchmark(
name: baseName + "First_NS",
regex: nsRegex,
type: .first,
target: input))
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/RegexBenchmark/BenchmarkRegistration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ extension BenchmarkRunner {
benchmark.addHTML()
benchmark.addEmail()
benchmark.addCustomCharacterClasses()
benchmark.addDna()
benchmark.addUnicode()
// -- end of registrations --
return benchmark
}
Expand Down
21 changes: 16 additions & 5 deletions Sources/RegexBenchmark/CLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ArgumentParser

@main
struct Runner: ParsableCommand {
@Argument(help: "Names of benchmarks to run")
@Argument(help: "Patterns for benchmarks to run")
var specificBenchmarks: [String] = []

@Flag(help: "Run only once for profiling purposes")
Expand All @@ -20,19 +20,30 @@ struct Runner: ParsableCommand {
@Flag(help: "Should the results be saved")
var save = false

@Flag(help: "Compare this result with the latest saved result")
@Flag(help: "Compare this result with a saved result")
var compare = false

@Option(help: "The result file to compare against, if this flag is not set it will compare against the most recent result file")
var compareFile: String?

@Flag(help: "Exclude the comparisons to NSRegex")
var excludeNs = false

mutating func run() throws {
var runner = BenchmarkRunner.makeRunner(samples, outputPath)

// todo: regex based filter

if !self.specificBenchmarks.isEmpty {
runner.suite = runner.suite.filter { b in specificBenchmarks.contains(b.name) }
runner.suite = runner.suite.filter { b in
specificBenchmarks.contains { pattern in
try! Regex(pattern).wholeMatch(in: b.name) != nil
}
}
}

if excludeNs {
runner.suite = runner.suite.filter { b in !b.name.contains("NS") }
}

switch (profile, debug) {
case (true, true): print("Cannot run both profile and debug")
case (true, false): runner.profile()
Expand Down
16,676 changes: 16,676 additions & 0 deletions Sources/RegexBenchmark/Inputs/DnaFASTA.swift

Large diffs are not rendered by default.

2,008 changes: 2,008 additions & 0 deletions Sources/RegexBenchmark/Inputs/TaggedUnicode.swift

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Sources/RegexBenchmark/Suite/CssRegex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension BenchmarkRunner {
let r = #"--([a-zA-Z0-9_-]+)\s*:\s*(.*?);"#

let css = CrossBenchmark(
baseName: "css", regex: r, input: Inputs.swiftOrgCSS)
baseName: "Css", regex: r, input: Inputs.swiftOrgCSS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I believe Swift's naming convention would prefer to keep them all capitalized or all lower case. Not a big deal to me.

css.register(&self)
}
}
12 changes: 6 additions & 6 deletions Sources/RegexBenchmark/Suite/CustomCharacterClasses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,37 @@ extension BenchmarkRunner {
let input = Inputs.graphemeBreakData

register(Benchmark(
name: "basicCCC",
name: "BasicCCC",
regex: try! Regex(basic),
type: .allMatches,
target: input))

register(Benchmark(
name: "basicRangeCCC",
name: "BasicRangeCCC",
regex: try! Regex(basicRange),
type: .allMatches,
target: input))

register(Benchmark(
name: "caseInsensitiveCCC",
name: "CaseInsensitiveCCC",
regex: try! Regex(caseInsensitive),
type: .allMatches,
target: input))

register(Benchmark(
name: "invertedCCC",
name: "InvertedCCC",
regex: try! Regex(inverted),
type: .allMatches,
target: input))

register(Benchmark(
name: "subtractionCCC",
name: "SubtractionCCC",
regex: try! Regex(subtraction),
type: .allMatches,
target: input))

register(Benchmark(
name: "intersectionCCC",
name: "IntersectionCCC",
regex: try! Regex(intersection),
type: .allMatches,
target: input))
Expand Down
25 changes: 25 additions & 0 deletions Sources/RegexBenchmark/Suite/Dna.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import _StringProcessing

extension BenchmarkRunner {
mutating func addDna() {
// regex-redux from the benchmarks game
// https://benchmarksgame-team.pages.debian.net/benchmarksgame/description/regexredux.html#regexredux
let dna = "agg[act]taaa|ttta[agt]cct"
let ends = "aND|caN|Ha[DS]|WaS"

let dnaMatching = CrossBenchmark(
baseName: "DnaMatch",
regex: dna,
input: Inputs.dnaFASTA,
includeFirst: true)

let sequenceEnds = CrossBenchmark(
baseName: "DnaEndsMatch",
regex: ends,
input: Inputs.dnaFASTA,
includeFirst: true)

dnaMatching.register(&self)
sequenceEnds.register(&self)
}
}
8 changes: 4 additions & 4 deletions Sources/RegexBenchmark/Suite/EmailRegex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ extension BenchmarkRunner {
let emailWithLookaheads = #"(?=[A-z0-9][A-z0-9@._%+-]{5,253})[A-z0-9._%+-]{1,64}@(?:(?=[A-z0-9-]{1,63}\.)[A-z0-9]+(?:-[A-z0-9]+)*\.){1,8}[A-z]{2,63}"#

let emailRFCValid = CrossBenchmark(
baseName: "emailRFC", regex: emailRFC, input: Inputs.validEmails)
baseName: "EmailRFC", regex: emailRFC, input: Inputs.validEmails)

let emailRFCInvalid = CrossBenchmark(
baseName: "emailRFCNoMatches",
baseName: "EmailRFCNoMatches",
regex: emailRFC,
input: Inputs.graphemeBreakData
)

let emailValid = CrossBenchmark(
baseName: "emailLookahead",
baseName: "EmailLookahead",
regex: emailWithLookaheads,
input: Inputs.validEmails
)

let emailInvalid = CrossBenchmark(
baseName: "emailLookaheadNoMatches",
baseName: "EmailLookaheadNoMatches",
regex: emailWithLookaheads,
input: Inputs.graphemeBreakData
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/RegexBenchmark/Suite/GraphemeBreak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension BenchmarkRunner {
let regex = #"HANGUL SYLLABLE [A-Z]+(?:\.\.HANGUL SYLLABLE [A-Z]+)?"#

let benchmark = CrossBenchmark(
baseName: "HangulSyllable", regex: regex, input: input)
baseName: "HangulSyllable", regex: regex, input: input, includeFirst: true)
benchmark.register(&self)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/RegexBenchmark/Suite/HtmlRegex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension BenchmarkRunner {
let r = #"<(\w*)\b[^>]*>(.*?)<\/\1>"#

let html = CrossBenchmark(
baseName: "html", regex: r, input: Inputs.swiftOrgHTML)
baseName: "Html", regex: r, input: Inputs.swiftOrgHTML)
html.register(&self)
}
}
4 changes: 2 additions & 2 deletions Sources/RegexBenchmark/Suite/NotFound.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ extension BenchmarkRunner {
let input = String(repeating: " ", count: 100_000)

let notFound = CrossBenchmark(
baseName: "notFound", regex: "a", input: input)
baseName: "NotFound", regex: "a", input: input)
notFound.register(&self)

let anchoredNotFound = CrossBenchmark(
baseName: "notFound", regex: "^ +a", input: input)
baseName: "AnchoredNotFound", regex: "^ +a", input: input)
anchoredNotFound.register(&self)
}
}
24 changes: 24 additions & 0 deletions Sources/RegexBenchmark/Suite/Unicode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import _StringProcessing

extension BenchmarkRunner {
mutating func addUnicode() {
// tagged unicode: unicode characters surrounded by html tags
// use the same html regex, uses backreference + reluctant quantification
let tags = #"<(\w*)\b[^>]*>(.*?)<\/\1>"#
let taggedEmojis = CrossBenchmark(
baseName: "TaggedEmojis",
regex: tags,
input: Inputs.taggedEmojis)

// Now actually matching emojis
let emoji = #"(😃|😀|😳|😲|😦|😊|🙊|😘|😏|😳|😒){2,5}"#

let emojiRegex = CrossBenchmark(
baseName: "EmojiRegex",
regex: emoji,
input: Inputs.taggedEmojis)

taggedEmojis.register(&self)
emojiRegex.register(&self)
}
}
File renamed without changes.
99 changes: 99 additions & 0 deletions Utils/benchmark-generators/generateFasta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# DnaFasta.swift was generated with python3 Utils/generateFasta.py 100000

# The Computer Language Benchmarks Game
# https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
#
# modified by Ian Osgood
# modified again by Heinrich Acker
# modified by Justin Peel
# 2to3

"""Copyright © 2004-2008 Brent Fulgham, 2005-2022 Isaac Gouy
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name "The Computer Language Benchmarks Game" nor the name "The Benchmarks Game" nor the name "The Computer Language Shootout Benchmarks" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentyrone Can you check off on licensing here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor Author

@rctcwyvrn rctcwyvrn Jun 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the script from here https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/fasta-python3-1.html and copied over the license from the repo here https://salsa.debian.org/benchmarksgame-team/benchmarksgame/-/blob/master/LICENSE.md

Not sure if the "redistributions of source code" applies only to the code in the repo for the website/benchmarking harness or for the scripts that users submit as well


import sys, bisect

alu = (
'GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGG'
'GAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGA'
'CCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAAT'
'ACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCA'
'GCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGG'
'AGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCC'
'AGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA')

iub = list(zip('acgtBDHKMNRSVWY', [0.27, 0.12, 0.12, 0.27] + [0.02]*11))

homosapiens = [
('a', 0.3029549426680),
('c', 0.1979883004921),
('g', 0.1975473066391),
('t', 0.3015094502008),
]


def genRandom(ia = 3877, ic = 29573, im = 139968):
seed = 42
imf = float(im)
while 1:
seed = (seed * ia + ic) % im
yield seed / imf

Random = genRandom()

def makeCumulative(table):
P = []
C = []
prob = 0.
for char, p in table:
prob += p
P += [prob]
C += [char]
return (P, C)

def repeatFasta(src, n):
width = 60
r = len(src)
s = src + src + src[:n % r]
for j in range(n // width):
i = j*width % r
print(s[i:i+width])
if n % width:
print(s[-(n % width):])

def randomFasta(table, n):
width = 60
r = range(width)
gR = Random.__next__
bb = bisect.bisect
jn = ''.join
probs, chars = makeCumulative(table)
for j in range(n // width):
x = jn([chars[bb(probs, gR())] for i in r])
print(x)
if n % width:
print(jn([chars[bb(probs, gR())] for i in range(n % width)]))

def main():
n = int(sys.argv[1])

print('>ONE Homo sapiens alu')
repeatFasta(alu, n*2)

print('>TWO IUB ambiguity codes')
randomFasta(iub, n*3)

print('>THREE Homo sapiens frequency')
randomFasta(homosapiens, n*5)

main()
Loading