Skip to content

Commit 531088a

Browse files
committed
Add performance measurement with instructions count
1 parent 680942e commit 531088a

File tree

11 files changed

+130
-50
lines changed

11 files changed

+130
-50
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

CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ extension LayoutNode {
8787
}
8888

8989
let formattedParams = """
90-
- Parameters:
91-
- leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. \
92-
If the node is empty, there is no token to attach the trivia to and the parameter is ignored.
93-
\(children.compactMap(generateParamDocComment).joined(separator: "\n"))
94-
- trailingTrivia: Trivia to be appended to the trailing trivia of the node’s last token. \
95-
If the node is empty, there is no token to attach the trivia to and the parameter is ignored.
96-
""".removingEmptyLines
90+
- Parameters:
91+
- leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. \
92+
If the node is empty, there is no token to attach the trivia to and the parameter is ignored.
93+
\(children.compactMap(generateParamDocComment).joined(separator: "\n"))
94+
- trailingTrivia: Trivia to be appended to the trailing trivia of the node’s last token. \
95+
If the node is empty, there is no token to attach the trivia to and the parameter is ignored.
96+
""".removingEmptyLines
9797

9898
return docCommentTrivia(from: formattedParams)
9999
}

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxCollectionsFile.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import Utils
1818
let syntaxCollectionsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
1919
for node in SYNTAX_NODES.compactMap(\.collectionNode) {
2020
let documentation = """
21-
\(node.documentation)
22-
\(node.documentation.isEmpty ? "" : "///")
23-
\(node.grammar)
24-
""".removingEmptyLines
21+
\(node.documentation)
22+
\(node.documentation.isEmpty ? "" : "///")
23+
\(node.grammar)
24+
""".removingEmptyLines
2525

2626
try! StructDeclSyntax(
2727
"""

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax {
3434
SourceFileSyntax(leadingTrivia: copyrightHeader) {
3535
for node in SYNTAX_NODES.compactMap(\.layoutNode) where node.base == emitKind {
3636
let documentation = """
37-
\(node.documentation)
38-
\(node.documentation.isEmpty ? "" : "///")
39-
\(node.grammar)
40-
""".removingEmptyLines
37+
\(node.documentation)
38+
\(node.documentation.isEmpty ? "" : "///")
39+
\(node.grammar)
40+
""".removingEmptyLines
4141

4242
// We are actually handling this node now
4343
try! StructDeclSyntax(

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class ParsingPerformanceTests: XCTestCase {
2525

2626
func testNativeParsingPerformance() throws {
2727
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
28-
measure {
28+
try measureInstructions {
2929
do {
3030
let source = try String(contentsOf: inputFile)
3131
_ = Parser.parse(source: source)

Tests/PerformanceTest/SyntaxClassifierPerformanceTests.swift

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

2727
func testClassifierPerformance() throws {
2828
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
29-
XCTAssertNoThrow(
30-
try {
29+
try measureInstructions {
30+
do {
3131
let source = try String(contentsOf: inputFile)
3232
let parsed = Parser.parse(source: source)
33-
34-
measure {
35-
for _ in 0..<10 {
36-
for _ in parsed.classifications {}
37-
}
33+
for _ in 0..<10 {
34+
for _ in parsed.classifications {}
3835
}
39-
}()
40-
)
36+
} catch {
37+
XCTFail(error.localizedDescription)
38+
}
39+
}
4140
}
4241
}

Tests/PerformanceTest/VisitorPerformanceTests.swift

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

30-
XCTAssertNoThrow(
31-
try {
30+
try measureInstructions {
31+
do {
3232
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
3333

3434
let emptyVisitor = EmptyVisitor(viewMode: .sourceAccurate)
35-
36-
measure {
37-
emptyVisitor.walk(parsed)
38-
}
39-
}()
40-
)
35+
emptyVisitor.walk(parsed)
36+
} catch {
37+
XCTFail(error.localizedDescription)
38+
}
39+
}
4140
}
4241

4342
func testEmptyRewriterPerformance() throws {
4443
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
4544
class EmptyRewriter: SyntaxRewriter {}
4645

47-
XCTAssertNoThrow(
48-
try {
46+
try measureInstructions {
47+
do {
4948
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
5049

5150
let emptyRewriter = EmptyRewriter(viewMode: .sourceAccurate)
52-
53-
measure {
54-
_ = emptyRewriter.visit(parsed)
55-
}
56-
}()
57-
)
51+
_ = emptyRewriter.visit(parsed)
52+
} catch {
53+
XCTFail(error.localizedDescription)
54+
}
55+
}
5856
}
5957

6058
func testEmptyAnyVisitorPerformance() throws {
6159
try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] == "1")
6260
class EmptyAnyVisitor: SyntaxAnyVisitor {}
6361

64-
XCTAssertNoThrow(
65-
try {
62+
try measureInstructions {
63+
do {
6664
let parsed = Parser.parse(source: try String(contentsOf: inputFile))
6765

6866
let emptyVisitor = EmptyAnyVisitor(viewMode: .sourceAccurate)
6967

70-
measure {
71-
emptyVisitor.walk(parsed)
72-
}
73-
}()
74-
)
68+
emptyVisitor.walk(parsed)
69+
} catch {
70+
XCTFail(error.localizedDescription)
71+
}
72+
}
7573
}
7674
}
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+
guard let baseline = baseline else {
36+
XCTFail("Missing baseline for \(strippedBaselineName)", file: file, line: line)
37+
return
38+
}
39+
40+
let startInstructions = getInstructionsExecuted()
41+
block()
42+
let endInstructions = getInstructionsExecuted()
43+
let numberOfInstructions = endInstructions - startInstructions
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+
fatalError("No baseline.json provided")
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)