Repository navigation
fix(parser): avoid quadratic diagnostic accumulation - #263
Merged
Merged
Conversation
fortmarek
requested review from
aleksandergrzyb,
ecamacho and
polac24
as code owners
October 27, 2026 16:33
pepicrft
approved these changes
Oct 27, 2026
This was referenced Oct 27, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Notice parsing repeatedly copies growing strings, arrays, and dictionaries. A diagnostic with a long continuation or a section with many messages therefore requires quadratic work even though its output is only proportional to its input. This makes large activity logs unnecessarily expensive to parse.
Matched release benchmarks below quantify all four affected paths while preserving their output. The regression fixtures also assert diagnostic contents, filtering, and ordering.
Root cause and approach
The previous diagnostic reduction interpolated the entire accumulated diagnostic for every continuation line. The notice reduction similarly evaluated
flatten + noticesfor each message. The location reduction copied its dictionary while accumulating diagnostics, and removing messages already classified as Clang warnings scanned every warning title for every message.This change removes those accumulation and lookup costs:
flatMap, preserving order without rebuilding the growing result for every message.No diagnostic output is truncated and no new input limit is imposed. The implementation preserves Unicode, ignored preamble, omitted empty carriage-return segments, last-occurrence-wins behavior for duplicate locations, and the existing preference for an error marker when both marker types occur in one diagnostic. Public APIs and serialization formats are unchanged.
Raising downstream timeouts would only move the failure threshold. Truncation would discard diagnostic evidence. Fixing accumulation in the library makes the improvement available to every consumer without requiring a consumer-specific workaround.
Before/after benchmarks
Both revisions were measured on the same Apple M3 Pro running macOS 26.3 with Apple Swift 6.4 (
swiftlang-6.4.0.34.1), using release builds. Before is unmodified implementationd723980ca700f96414c37d37697f96e8cdeff20f; after is this PR's687d3869687c80eaf57b093151968acd3ce3eb2f. The exact same four synthetic test fixtures were used for both revisions. No private activity logs are required to reproduce these cases.Each cell is the median of three separate test-process runs, measured by the tests' existing
Date()timer immediately around the parser call. Compilation, fixture construction, and output assertions are excluded. Runs were sequential, with all after samples collected before all before samples; no separate warm-up was discarded. These are local parser benchmarks, not end-to-end activity-log timings or a guarantee for other machines.Raw samples in execution order (seconds):
To reproduce, use separate checkouts at the two revisions, copy this PR's
Tests/XCLogParserTests/ParserTests.swiftinto the baseline checkout, and temporarily addprint("BENCHMARK \(#function) seconds=\(elapsed)")after each of its fourlet elapsed = Date().timeIntervalSince(start)statements. Run the following in each checkout, then repeat with--skip-buildfor samples two and three:Keep the test assertions enabled: output-content and ordering checks validate the result alongside the timer. The baseline can exit unsuccessfully because these regression tests intentionally impose a ten-second parser ceiling; its printed elapsed time still records the completed parser call. The temporary timing prints are not part of the committed change.
Tests and validation
Seven new tests cover compatibility and four large-input shapes: a diagnostic with 40,000 continuation lines of 1 KiB each, 40,000 distinct locations, 60,000 notices, and 40,000 Clang warnings with a repeated title and an unmatched note. Performance guards use a generous ten-second ceiling rather than a tight microbenchmark threshold. All fixtures are synthetic and created in the tests. The new tests are registered in the existing Linux manifest without changing its unrelated test inventory.
swift testpassed all 95 tests. The notice-volume test completed in 0.26 seconds; the large-continuation test completed in 4.48 seconds including fixture construction and full-output comparison.swift:5.7.1, matching the upstream CI toolchain version, in a local ARM64 container. The large-continuation test completed in 3.04 seconds and the notice-volume test in 0.32 seconds.LogFinderTests.testGetLogManifestPathWithWorkspace. The cause of the local-container stall was not established. The complete Linux suite is therefore not claimed as passing; upstream CI runs on AMD64.git diff --checkpassed.Compatibility and rollout
Ship this in a library release and let consumers update their dependency normally. No data migration, API transition, or wire-format change is required. Existing timeout and retry policies can remain in place. Reverting the dependency version restores the previous behavior, including its poor performance on the synthetic large-input cases.