Skip to content

Commit 7ebe7f3

Browse files
committed
Add performance measurement with instructions count
1 parent 680942e commit 7ebe7f3

8 files changed

+116
-47
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ Package.resolved
1818

1919
.DS_Store
2020
*.pyc
21+
22+
Tests/PerformanceTest/baselines.json

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,8 @@ let package = Package(
283283

284284
.testTarget(
285285
name: "PerformanceTest",
286-
dependencies: ["SwiftIDEUtils", "SwiftParser", "SwiftSyntax"],
287-
exclude: ["Inputs"]
286+
dependencies: ["_InstructionCounter", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax"],
287+
exclude: ["Inputs", "ci-baselines.json"]
288288
),
289289
]
290290
)

Tests/PerformanceTest/ParsingPerformanceTests.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@ public class ParsingPerformanceTests: XCTestCase {
2525

2626
func testNativeParsingPerformance() throws {
2727
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
28-
measure {
29-
do {
30-
let source = try String(contentsOf: inputFile)
31-
_ = Parser.parse(source: source)
32-
} catch {
33-
XCTFail(error.localizedDescription)
34-
}
28+
29+
let source = try String(contentsOf: inputFile)
30+
31+
try measureInstructions {
32+
_ = Parser.parse(source: source)
3533
}
3634
}
3735
}

Tests/PerformanceTest/SyntaxClassifierPerformanceTests.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,14 @@ public class SyntaxClassifierPerformanceTests: XCTestCase {
2626

2727
func testClassifierPerformance() throws {
2828
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
29-
XCTAssertNoThrow(
30-
try {
31-
let source = try String(contentsOf: inputFile)
32-
let parsed = Parser.parse(source: source)
3329

34-
measure {
35-
for _ in 0..<10 {
36-
for _ in parsed.classifications {}
37-
}
38-
}
39-
}()
40-
)
30+
let source = try String(contentsOf: inputFile)
31+
let parsed = Parser.parse(source: source)
32+
33+
try measureInstructions {
34+
for _ in 0..<10 {
35+
for _ in parsed.classifications {}
36+
}
37+
}
4138
}
4239
}

Tests/PerformanceTest/VisitorPerformanceTests.swift

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,50 +27,41 @@ public class VisitorPerformanceTests: XCTestCase {
2727
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
2828
class EmptyVisitor: SyntaxVisitor {}
2929

30-
XCTAssertNoThrow(
31-
try {
32-
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
30+
let source = try String(contentsOf: inputFile)
31+
let parsed = Parser.parse(source: source)
3332

34-
let emptyVisitor = EmptyVisitor(viewMode: .sourceAccurate)
33+
try measureInstructions {
3534

36-
measure {
37-
emptyVisitor.walk(parsed)
38-
}
39-
}()
40-
)
35+
let emptyVisitor = EmptyVisitor(viewMode: .sourceAccurate)
36+
emptyVisitor.walk(parsed)
37+
}
4138
}
4239

4340
func testEmptyRewriterPerformance() throws {
4441
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
4542
class EmptyRewriter: SyntaxRewriter {}
4643

47-
XCTAssertNoThrow(
48-
try {
49-
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
44+
let source = try String(contentsOf: inputFile)
45+
let parsed = Parser.parse(source: source)
5046

51-
let emptyRewriter = EmptyRewriter(viewMode: .sourceAccurate)
47+
try measureInstructions {
5248

53-
measure {
54-
_ = emptyRewriter.visit(parsed)
55-
}
56-
}()
57-
)
49+
let emptyRewriter = EmptyRewriter(viewMode: .sourceAccurate)
50+
_ = emptyRewriter.visit(parsed)
51+
}
5852
}
5953

6054
func testEmptyAnyVisitorPerformance() throws {
6155
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
6256
class EmptyAnyVisitor: SyntaxAnyVisitor {}
6357

64-
XCTAssertNoThrow(
65-
try {
66-
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
58+
let source = try String(contentsOf: inputFile)
59+
let parsed = Parser.parse(source: source)
6760

68-
let emptyVisitor = EmptyAnyVisitor(viewMode: .sourceAccurate)
61+
try measureInstructions {
62+
let emptyVisitor = EmptyAnyVisitor(viewMode: .sourceAccurate)
6963

70-
measure {
71-
emptyVisitor.walk(parsed)
72-
}
73-
}()
74-
)
64+
emptyVisitor.walk(parsed)
65+
}
7566
}
7667
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import XCTest
14+
import _InstructionCounter
15+
16+
extension XCTestCase {
17+
private var baselineURL: URL {
18+
if let baselineFile = ProcessInfo.processInfo.environment["BASELINE_FILE"] {
19+
return URL(fileURLWithPath: baselineFile)
20+
} else {
21+
return URL(fileURLWithPath: #file)
22+
.deletingLastPathComponent()
23+
.appendingPathComponent("baselines.json")
24+
}
25+
}
26+
27+
func measureInstructions(_ baselineName: StaticString = #function, block: () -> Void, file: StaticString = #file, line: UInt = #line) throws {
28+
let strippedBaselineName = "\(baselineName)".replacingOccurrences(of: "()", with: "")
29+
var baseline: UInt64? = try baseline(for: strippedBaselineName)
30+
31+
if let environmentBaseline = ProcessInfo.processInfo.environment[strippedBaselineName] {
32+
baseline = UInt64(environmentBaseline)
33+
}
34+
35+
let startInstructions = getInstructionsExecuted()
36+
block()
37+
let endInstructions = getInstructionsExecuted()
38+
let numberOfInstructions = endInstructions - startInstructions
39+
40+
guard let baseline = baseline else {
41+
XCTFail("Missing baseline for \(strippedBaselineName) with number of instructions \(numberOfInstructions)", file: file, line: line)
42+
return
43+
}
44+
45+
let lowerBaseline = UInt64(Double(baseline) * 0.97)
46+
let upperBaseline = UInt64(Double(baseline) * 1.03)
47+
let baselineRange = lowerBaseline...upperBaseline
48+
49+
XCTAssertTrue(
50+
baselineRange.contains(numberOfInstructions),
51+
"""
52+
Number of instructions '\(numberOfInstructions)'
53+
is not within range \(lowerBaseline)-\(upperBaseline)
54+
""",
55+
file: file,
56+
line: line
57+
)
58+
}
59+
60+
private func baseline(for key: String) throws -> UInt64? {
61+
let baselineData = try? Data(contentsOf: baselineURL)
62+
63+
guard let data = baselineData else {
64+
return nil
65+
}
66+
67+
let jsonDecoder = JSONDecoder()
68+
let baselineMap = try jsonDecoder.decode([String: UInt64].self, from: data)
69+
70+
return baselineMap[key]
71+
}
72+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"testNativeParsingPerformance": 5850081534,
3+
"testClassifierPerformance": 27177168966,
4+
"testEmptyVisitorPerformance": 6641646112,
5+
"testEmptyRewriterPerformance": 6641646112,
6+
"testEmptyAnyVisitorPerformance": 6711819049
7+
}

build-script.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
WORKSPACE_DIR = os.path.dirname(PACKAGE_DIR)
1717
EXAMPLES_DIR = os.path.join(PACKAGE_DIR, "Examples")
1818
SOURCES_DIR = os.path.join(PACKAGE_DIR, "Sources")
19+
TESTS_DIR = os.path.join(PACKAGE_DIR, "Tests")
1920
SWIFTIDEUTILS_DIR = os.path.join(SOURCES_DIR, "SwiftIDEUtils")
2021
SWIFTSYNTAX_DIR = os.path.join(SOURCES_DIR, "SwiftSyntax")
2122
SWIFTSYNTAX_DOCUMENTATION_DIR = \
@@ -236,6 +237,7 @@ def __build(self, package_dir: str, name: str, is_product: bool) -> None:
236237
env["SWIFTCI_USE_LOCAL_DEPS"] = "1"
237238
env["SWIFT_SYNTAX_PARSER_LIB_SEARCH_PATH"] = \
238239
os.path.join(self.toolchain, "lib", "swift", "macosx")
240+
env["BASELINE_FILE"] = os.path.join(TESTS_DIR, "PerformanceTest", "ci-baselines.json")
239241
check_call(command, env=env, verbose=self.verbose)
240242

241243

0 commit comments

Comments
 (0)