diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/PerformanceTests.xcbaseline/1868159C-3A7D-4BC9-A194-7BBFC5CE3264.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/PerformanceTests.xcbaseline/1868159C-3A7D-4BC9-A194-7BBFC5CE3264.plist index 025da2e..becdfe6 100644 --- a/.swiftpm/xcode/xcshareddata/xcbaselines/PerformanceTests.xcbaseline/1868159C-3A7D-4BC9-A194-7BBFC5CE3264.plist +++ b/.swiftpm/xcode/xcshareddata/xcbaselines/PerformanceTests.xcbaseline/1868159C-3A7D-4BC9-A194-7BBFC5CE3264.plist @@ -134,6 +134,212 @@ + StringTests + + testAnyNumeral() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.3081e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testContainsClosure() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.5442e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testGrammarLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 6.0042e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testLine() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.7144e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 6.3992e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testNonExistentLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 3.4154e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testNotNewLine() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.5535e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testOneOrMore() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 9.2286e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testOptionalStringFollowedByNonOptionalString() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.0759e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testSkipping1() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 1.1965e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testUppercaseWord() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 2.9068e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testWordBoundary() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 2.5362e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testWordBoundaryManyLanguages() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 3.7748e+06 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + + UTF8Tests + + testGrammarLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 4.1135e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testLine() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 9.6233e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 8.3321e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testNonExistentLiteralSearch() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 6.0671e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testNotNewLine() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 7.4556e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testOptionalStringFollowedByNonOptionalString() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 8.219e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + testSkipping1() + + com.apple.dt.XCTMetric_CPU.instructions_retired + + baselineAverage + 2.2521e+05 + baselineIntegrationDisplayName + 19 Aug 2020 at 20:14:16 + + + diff --git a/Sources/Patterns/Atomic Patterns/Line.swift b/Sources/Patterns/Atomic Patterns/Line.swift index fd04c89..ac556b8 100644 --- a/Sources/Patterns/Atomic Patterns/Line.swift +++ b/Sources/Patterns/Atomic Patterns/Line.swift @@ -6,6 +6,7 @@ // public protocol CharacterLike: Hashable { + @inlinable var isNewline: Bool { get } } diff --git a/Sources/Patterns/Operations on Patterns/Skip.swift b/Sources/Patterns/Operations on Patterns/Skip.swift index 6c3a670..8518242 100644 --- a/Sources/Patterns/Operations on Patterns/Skip.swift +++ b/Sources/Patterns/Operations on Patterns/Skip.swift @@ -48,8 +48,10 @@ public struct Skip: Pattern where Input.Element: import SE0270_RangeSet -extension MutableCollection where Self: RandomAccessCollection, Self: RangeReplaceableCollection, Index == Int { +extension ContiguousArray { /// Replaces all placeholder `.skip` instructions. + @_specialize(where Input == String, Element == Instruction) // doesn't happen automatically (swiftlang-1200.0.28.1). + @_specialize(where Input == String.UTF8View, Element == Instruction) @usableFromInline mutating func replaceSkips() where Element == Instruction { // `setupSkip(at: i)` adds 1 new instruction somewhere after `ì`, so we cant loop over self.indices directly diff --git a/Sources/Patterns/Pattern And Instruction.swift b/Sources/Patterns/Pattern And Instruction.swift index 359ebfd..3c985f4 100644 --- a/Sources/Patterns/Pattern And Instruction.swift +++ b/Sources/Patterns/Pattern And Instruction.swift @@ -148,7 +148,7 @@ public enum Instruction where Input.Element: Has extension Sequence { /// The offset by which these instructions will move the input index. - @inlinable + @usableFromInline func movesIndexBy

() -> Int? where Element == Instruction

{ lazy .map { $0.movesIndexBy }.reduceIfNoNils(into: 0) { result, offset in result += offset } } diff --git a/Sources/Patterns/VMBacktrack.swift b/Sources/Patterns/VMBacktrack.swift index 3fd0ef9..c10cac8 100644 --- a/Sources/Patterns/VMBacktrack.swift +++ b/Sources/Patterns/VMBacktrack.swift @@ -9,6 +9,7 @@ struct VMEngine where Input.Element: Hashable { @usableFromInline typealias Instructions = ContiguousArray> + @usableFromInline let instructions: Instructions @usableFromInline @@ -23,6 +24,8 @@ struct VMEngine where Input.Element: Hashable { self.instructions = instructions } + @_specialize(where Input == String) // doesn't happen automatically (swiftlang-1200.0.28.1). + @_specialize(where Input == String.UTF8View) @usableFromInline func match(in input: Input, at startIndex: Input.Index) -> Parser.Match? { launch(input: input, startIndex: startIndex) @@ -56,9 +59,13 @@ extension Parser.Match { extension VMEngine { @usableFromInline struct Thread { + @usableFromInline var instructionIndex: Instructions.Index + @usableFromInline var inputIndex: Input.Index + @usableFromInline var captures: ContiguousArray<(index: Input.Index, instruction: Instructions.Index)> + @usableFromInline var isReturnAddress: Bool = false @usableFromInline diff --git a/Tests/PerformanceTests/PerformanceTests.swift b/Tests/PerformanceTests/StringTests.swift similarity index 70% rename from Tests/PerformanceTests/PerformanceTests.swift rename to Tests/PerformanceTests/StringTests.swift index 264159e..df70f9d 100644 --- a/Tests/PerformanceTests/PerformanceTests.swift +++ b/Tests/PerformanceTests/StringTests.swift @@ -12,29 +12,7 @@ import XCTest // Note: the hits parameter to speedTest doesn't necessarily mean the _correct_ number of hits. // It's just there to notify us when the number of hits changes. -class PerformanceTests: XCTestCase { - func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int, - file: StaticString = #filePath, line: UInt = #line) throws { - let fulltext = try String(contentsOf: getLocalURL(for: testFile)) - let text = String(fulltext.prefix(fulltext.count / textFraction)).utf8 - var result = 0 - let block = { - result = pattern.matches(in: text).reduce(into: 0) { c, _ in c += 1 } - } - #if DEBUG - block() - #else - if #available(OSX 10.15, *) { - let options = XCTMeasureOptions() - options.iterationCount = 10 - self.measure(metrics: [XCTCPUMetric(limitingToCurrentThread: true)], options: options, block: block) - } else { - self.measure(block) - } - #endif - XCTAssertEqual(result, hits, file: file, line: line) - } - +class StringTests: XCTestCase { func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int, file: StaticString = #filePath, line: UInt = #line) throws { let fulltext = try String(contentsOf: getLocalURL(for: testFile)) @@ -73,36 +51,36 @@ class PerformanceTests: XCTestCase { } func testLine() throws { - let pattern = try Parser(search: Line.Start() • Capture(Skip()) • Line.End()) + let pattern = try Parser(search: Line.start • Capture(Skip()) • Line.end) try speedTest(pattern, textFraction: 2, hits: 7260) } func testNotNewLine() throws { - let pattern = try Parser(search: "," • Capture(Skip()) • Line.End()) + let pattern = try Parser(search: "," • Capture(Skip()) • Line.End()) try speedTest(pattern, textFraction: 2, hits: 4933) } func testLiteralSearch() throws { - let pattern = try Parser(search: Literal("Prince")) + let pattern = try Parser(search: Literal("Prince")) try speedTest(pattern, textFraction: 1, hits: 2168) } func testGrammarLiteralSearch() throws { func any() -> OneOf { OneOf(description: "any", contains: { _ in true }) } - let g = Grammar() + let g = Grammar() g.a <- Capture("Prince") / any() • g.a let pattern = try Parser(g) try speedTest(pattern, textFraction: 13, hits: 260) } func testNonExistentLiteralSearch() throws { - let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST") + let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST") try speedTest(pattern, textFraction: 1, hits: 0) } func testOptionalStringFollowedByNonOptionalString() throws { - let pattern = try Parser(search: Literal("\"")¿ • "I") + let pattern = try Parser(search: "\""¿ • "I") try speedTest(pattern, textFraction: 12, hits: 814) } @@ -113,7 +91,7 @@ class PerformanceTests: XCTestCase { func testSkipping1() throws { // [ word.boundary ] * " " * ":" * " " * " " * " " * "{" * Line.end - let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ") + let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ") try speedTest(pattern, textFraction: 2, hits: 13939) } diff --git a/Tests/PerformanceTests/UTF8Tests.swift b/Tests/PerformanceTests/UTF8Tests.swift new file mode 100644 index 0000000..1a32480 --- /dev/null +++ b/Tests/PerformanceTests/UTF8Tests.swift @@ -0,0 +1,77 @@ +// +// File.swift +// +// +// Created by Kåre Morstøl on 19/08/2020. +// + +import Foundation +import Patterns +import XCTest + +// Note: the hits parameter to speedTest doesn't necessarily mean the _correct_ number of hits. +// It's just there to notify us when the number of hits changes. + +class UTF8Tests: XCTestCase { + func speedTest(_ pattern: Parser, testFile: String = "Long.txt", textFraction: Int = 1, hits: Int, + file: StaticString = #filePath, line: UInt = #line) throws { + let fulltext = try String(contentsOf: getLocalURL(for: testFile)) + let text = String(fulltext.prefix(fulltext.count / textFraction)).utf8 + var result = 0 + let block = { + result = pattern.matches(in: text).reduce(into: 0) { c, _ in c += 1 } + } + #if DEBUG + block() + #else + if #available(OSX 10.15, *) { + let options = XCTMeasureOptions() + options.iterationCount = 10 + self.measure(metrics: [XCTCPUMetric(limitingToCurrentThread: true)], options: options, block: block) + } else { + self.measure(block) + } + #endif + XCTAssertEqual(result, hits, file: file, line: line) + } + + func testLine() throws { + let pattern = try Parser(search: Line.Start() • Capture(Skip()) • Line.End()) + try speedTest(pattern, textFraction: 2, hits: 7260) + } + + func testNotNewLine() throws { + let pattern = try Parser(search: "," • Capture(Skip()) • Line.End()) + try speedTest(pattern, textFraction: 2, hits: 4933) + } + + func testLiteralSearch() throws { + let pattern = try Parser(search: Literal("Prince")) + try speedTest(pattern, textFraction: 1, hits: 2168) + } + + func testGrammarLiteralSearch() throws { + func any() -> OneOf { OneOf(description: "any", contains: { _ in true }) } + + let g = Grammar() + g.a <- Capture("Prince") / any() • g.a + let pattern = try Parser(g) + try speedTest(pattern, textFraction: 13, hits: 260) + } + + func testNonExistentLiteralSearch() throws { + let pattern = try Parser(search: "\n" • Skip() • "DOESN'T EXIST") + try speedTest(pattern, textFraction: 1, hits: 0) + } + + func testOptionalStringFollowedByNonOptionalString() throws { + let pattern = try Parser(search: Literal("\"")¿ • "I") + try speedTest(pattern, textFraction: 12, hits: 814) + } + + func testSkipping1() throws { + // [ word.boundary ] * " " * ":" * " " * " " * " " * "{" * Line.end + let pattern = try Parser(search: "." • Skip() • " " • Skip() • " ") + try speedTest(pattern, textFraction: 2, hits: 13939) + } +}