-
Notifications
You must be signed in to change notification settings - Fork 414
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
Add AsyncSyntaxRewriter
#2818
base: main
Are you sure you want to change the base?
Add AsyncSyntaxRewriter
#2818
Conversation
Could you check how the performance of |
Results
|
func testEmptyRewriterPerformance() throws { | |
try XCTSkipIf(longTestsDisabled) | |
class EmptyRewriter: SyntaxRewriter {} | |
let source = try String(contentsOf: inputFile, encoding: .utf8) | |
let parsed = Parser.parse(source: source) | |
let emptyRewriter = EmptyRewriter(viewMode: .sourceAccurate) | |
try measureInstructions { | |
_ = emptyRewriter.rewrite(parsed) | |
} | |
} |
swift-syntax/Tests/PerformanceTest/InstructionsCountAssertion.swift
Lines 26 to 74 in fe1a312
func measureInstructions( | |
_ baselineName: StaticString = #function, | |
block: () -> Void, | |
file: StaticString = #filePath, | |
line: UInt = #line | |
) throws { | |
let startInstructions = getInstructionsExecuted() | |
block() | |
let endInstructions = getInstructionsExecuted() | |
let numberOfInstructions = endInstructions - startInstructions | |
let strippedBaselineName = "\(baselineName)".replacingOccurrences(of: "()", with: "") | |
// Performance testing is only supported on macOS. | |
// On all other platforms `getInstructionsExecuted` returns 0. | |
#if os(macOS) | |
// If the is no data, we just continue the test | |
guard let data = try? Data(contentsOf: baselineURL) else { | |
return | |
} | |
let jsonDecoder = JSONDecoder() | |
let baselineMap = try jsonDecoder.decode([String: UInt64].self, from: data) | |
guard let baseline = baselineMap[strippedBaselineName] else { | |
XCTFail( | |
""" | |
Missing baseline for \(strippedBaselineName) | |
with number of instructions '\(numberOfInstructions)' | |
""", | |
file: file, | |
line: line | |
) | |
return | |
} | |
let relativeDeviation = Double(numberOfInstructions) / Double(baseline) - 1 | |
let allowedDeviation = 0.01 | |
XCTAssertTrue( | |
(-allowedDeviation..<allowedDeviation).contains(relativeDeviation), | |
""" | |
Number of instructions '\(numberOfInstructions)' deviated from baseline by \(String(format: "%.4f", relativeDeviation * 100))%. | |
The maximum allowed deviation for '\(strippedBaselineName)' is \(allowedDeviation * 100)% in either direction. | |
""", | |
file: file, | |
line: line | |
) | |
#endif | |
} |
testEmptyAsyncRewriterPerformance
func testEmptyAsyncRewriterPerformance() async throws {
try XCTSkipIf(longTestsDisabled)
class EmptyAsyncRewriter: AsyncSyntaxRewriter {}
let source = try String(contentsOf: inputFile, encoding: .utf8)
let parsed = Parser.parse(source: source)
let emptyRewriter = EmptyAsyncRewriter(viewMode: .sourceAccurate)
try await measureInstructions {
_ = await emptyRewriter.rewrite(parsed)
}
}
func measureInstructions(
_ baselineName: StaticString = #function,
block: () async -> Void,
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let startInstructions = getInstructionsExecuted()
await block()
let endInstructions = getInstructionsExecuted()
let numberOfInstructions = endInstructions - startInstructions
let strippedBaselineName = "\(baselineName)".replacingOccurrences(of: "()", with: "")
// Performance testing is only supported on macOS.
// On all other platforms `getInstructionsExecuted` returns 0.
#if os(macOS)
// If the is no data, we just continue the test
guard let data = try? Data(contentsOf: baselineURL) else {
return
}
let jsonDecoder = JSONDecoder()
let baselineMap = try jsonDecoder.decode([String: UInt64].self, from: data)
guard let baseline = baselineMap[strippedBaselineName] else {
XCTFail(
"""
Missing baseline for \(strippedBaselineName)
with number of instructions '\(numberOfInstructions)'
""",
file: file,
line: line
)
return
}
let relativeDeviation = Double(numberOfInstructions) / Double(baseline) - 1
let allowedDeviation = 0.01
XCTAssertTrue(
(-allowedDeviation..<allowedDeviation).contains(relativeDeviation),
"""
Number of instructions '\(numberOfInstructions)' deviated from baseline by \(String(format: "%.4f", relativeDeviation * 100))%.
The maximum allowed deviation for '\(strippedBaselineName)' is \(allowedDeviation * 100)% in either direction.
""",
file: file,
line: line
)
#endif
}
Log
testEmptyRewriterPerformance
Test Suite 'Selected tests' started at 2024-08-26 19:26:08.230.
Test Suite 'PerformanceTest.xctest' started at 2024-08-26 19:26:08.230.
Test Suite 'VisitorPerformanceTests' started at 2024-08-26 19:26:08.230.
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' started (Iteration 1 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' passed (0.483 seconds).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' started (Iteration 2 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' passed (0.468 seconds).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' started (Iteration 3 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' passed (0.463 seconds).
...
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' started (Iteration 100 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyRewriterPerformance]' passed (0.461 seconds).
Test Suite 'VisitorPerformanceTests' passed at 2024-08-26 19:26:54.485.
Executed 100 tests, with 0 failures (0 unexpected) in 46.238 (46.255) seconds
Test Suite 'PerformanceTest.xctest' passed at 2024-08-26 19:26:54.485.
Executed 100 tests, with 0 failures (0 unexpected) in 46.238 (46.255) seconds
Test Suite 'Selected tests' passed at 2024-08-26 19:26:54.485.
Executed 100 tests, with 0 failures (0 unexpected) in 46.238 (46.255) seconds
Program ended with exit code: 0
testEmptyAsyncRewriterPerformance
Test Suite 'Selected tests' started at 2024-08-26 19:24:18.137.
Test Suite 'PerformanceTest.xctest' started at 2024-08-26 19:24:18.137.
Test Suite 'VisitorPerformanceTests' started at 2024-08-26 19:24:18.137.
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' started (Iteration 1 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' passed (0.494 seconds).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' started (Iteration 2 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' passed (0.478 seconds).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' started (Iteration 3 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' passed (0.476 seconds).
...
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' started (Iteration 100 of 100).
Test Case '-[PerformanceTest.VisitorPerformanceTests testEmptyAsyncRewriterPerformance]' passed (0.475 seconds).
Test Suite 'VisitorPerformanceTests' passed at 2024-08-26 19:25:05.681.
Executed 100 tests, with 0 failures (0 unexpected) in 47.529 (47.543) seconds
Test Suite 'PerformanceTest.xctest' passed at 2024-08-26 19:25:05.681.
Executed 100 tests, with 0 failures (0 unexpected) in 47.529 (47.544) seconds
Test Suite 'Selected tests' passed at 2024-08-26 19:25:05.681.
Executed 100 tests, with 0 failures (0 unexpected) in 47.529 (47.544) seconds
Program ended with exit code: 0
(the results in the above comment were obtained running in debug mode) Results running the tests in release mode:
Both of the rewriters traverse the syntax tree serially with no parallelism, so it makes sense that the async one is |
Oh, wow. 40% performance decrease in the CC @rintaro |
I guess the question is, would it be useful to expose If it's decided that |
Yeah, it's not great... Good thing you brought up the topic of performance. I'll try to come up with a non-recursive solution ditching |
This is a prerequisite for implementing async overloads of
assertMacroExpansion
and the variousexpansion
functions discussed with @ahoppen at #2803 (comment). Specifically,AsyncSyntaxRewriter
is required to be able to implement an asynchronous version ofMacroApplication
:swift-syntax/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift
Line 659 in 16dc4f8
AsyncSyntaxRewriter
has the same visibility asSyntaxRewriter
, but in terms ofMacroApplication
, the visibility ofAsyncSyntaxRewriter
could be changed tointernal
if its declaration was moved fromSwiftSyntax
toSwiftSyntaxMacroExpansion
module.Changes made to
SyntaxRewriter
code generation also make it easy to generatethrows
andasync throws
versions ofSyntaxRewriter
if they are at some point desired.Another potential name for
AsyncSyntaxRewriter
could beSyntaxAsyncRewriter
, similar toSyntaxAnyVisitor
, but I am not sure which one is better.