Reduce key-path COW copies in Runner.Plan helpers#1730
Conversation
Following the same pattern as swiftlang#1725, thread `nameComponents` through `_recursivelySynthesizeSuites`'s inner recursion as `inout` with a push/pop discipline. Avoids the COW copy that `nameComponents + [key]` triggered on every recursive descent. In isolated micro-benchmarks at depths 3-6 with branching 3-5, the new version is 4.47x-5.68x faster -- same caliber and shape as swiftlang#1725. The helper runs once per test run during plan construction, so absolute savings are small (sub-millisecond on typical test plans), but the change removes one more instance of the pattern there. All existing tests in `PlanTests`, `Runner.Plan.SnapshotTests`, `RunnerTests`, and the wider suite/trait/tag tests pass.
|
Hi @grynspan @harlanhaskins, sorry to bother you both — reviewers didn't auto-assign on this PR (unlike on my previous one). I'm not sure why, but I noticed a 500 error from GitHub when I opened the PR, so maybe that's the cause? No urgency at all, just flagging in case it needs a manual nudge. Thanks! |
|
Thanks for looking further into this! In this specific case, looking at the microbenchmarks, I think the complexity difference might not be worth the perf improvements given we're dealing in sub-ms times. But I'm curious what @grynspan thinks. |
|
Thanks for the honest read — agreed the sub-ms perf wins on their own don't really justify the extra complexity in commit 2. I will say the motivation was as much about code consistency as raw speed: both helpers had the same COW shape that #1725 had just cleaned up in Graph, so leaving them in the older style felt incomplete. But that's a soft argument and totally up to you both. If it'd help, I'm happy to drop commit 2 and keep just commit 1 (which is a mechanical match for #1725 and has no real complexity tradeoff), or close this PR entirely. Whatever you both think is best. |
|
Let's try to land the first commit, and you can follow up with the second commit in a separate PR. |
5460e63 to
45f5935
Compare
|
@grynspan |
Motivation
After landing #1725, I went back through the rest of the codebase looking for the same "key-path COW" shape applied elsewhere — i.e. recursive functions that build a per-descent collection with
something + [key]and pass it by value to the recursion._recursivelySynthesizeSuitesinSources/Testing/Running/Runner.Plan.swiftis one such instance: it threads anameComponents: [String]path down to every node and rebuilds it on every descent withnameComponents + [key].This is the same shape that #1725 fixed in
Graph._forEach/Graph._compactMapValues:This runs at plan construction (not during the test run), so the absolute savings are smaller than #1725 — but the pattern is the same, the fix is the same, and the existing tests cover the call site.
Modifications
Pure mechanical match for #1725: thread
nameComponentsthrough an inner recursive function asinout. The for loop pushes the key, recurses, pops the key. By the time control returns to the lines that consumenameComponents(the suite-synthesis block at the bottom), the array is back to the value it had on entry.No public API changes.
Measurements
Isolated micro-benchmark of the recursive pattern at depths 3–6 with branching 3–5:
The speedup grows with depth, as expected from O(N · d) → O(N).
This is a plan-construction helper, called once per test run — so the absolute wins are small. The change is the same kind of cleanup as #1725 in spirit.
All existing tests in
PlanTests,Runner.Plan.SnapshotTests,RunnerTests, and the wider suite/trait/tag tests pass unchanged.Checklist