Improve TableLayout ReservationGrid row advancement performance - #15065
Open
Eales wants to merge 1 commit into
Open
Improve TableLayout ReservationGrid row advancement performance#15065Eales wants to merge 1 commit into
Eales wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #15065 +/- ##
=============================================
Coverage 37.24166% 37.24166%
=============================================
Files 246 246
Lines 9774 9774
Branches 1029 1029
=============================================
Hits 3640 3640
Misses 5970 5970
Partials 164 164
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
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.
Fixes #15066
Proposed changes
Improve
TableLayout.ReservationGridrow advancement by maintaining a logical head into the existing row list and compacting the discarded prefix only periodically.This changes repeated row advancement from quadratic reference movement to amortized linear work while preserving the existing reservation semantics.
List<BitArray>, private visibility, and reservation semantics. No public API changes, pooling, or custom collection.Previously, draining R reserved rows shifted R(R-1)/2 references through
RemoveAt(0). Each compaction now moves no more live rows than the number consumed since the previous compaction: amortized O(1) per advancement and O(R) for a drain.Customer Impact
Layouts with deep row spans and many subsequently placed controls spend less time advancing through reservations. This removes one quadratic component of
TableLayoutPanellayout; it does not make every part of layout linear.In the measured two-column, 4,096-row public workload, layout time decreased from 3.484 ms to 1.904 ms (about 45%). Smaller/common layouts showed no consistent slowdown in a separate A/B/B/A check. These are workload-specific, single-machine measurements, not a general application speedup guarantee.
No intended changes to control placement, row/column sizes, visual output, or accessibility behavior.
Regression?
No known release regression. This addresses an existing algorithmic performance limitation.
The large-offset tests protect compatibility during this refactor; they are not a claim that the baseline throws for those inputs.
Risk
The change is localized, but an indexing error could affect layout placement. Tests cover advancing, appending after a nonzero head, exhaustion/reset, compaction, multiple columns, differential state checks, and a public six-control case with a very large explicitly assigned row index.
There is a memory tradeoff: consumed
BitArrayreferences remain reachable until compaction/reset. After each operation, a nonempty grid has fewer than twice as many stored rows as live rows. This is a bound on stored references, not on retainedList.Capacity. Sustained sliding windows can grow the backing array compared with the baseline (observed capacity 4 -> 8 at depth 4, and 128 -> 256 at depth 128). No unbounded accumulation was observed in the rolling-window checks.Test methodology
dotnet formatreported referenced-project workspace-loading warnings; the actual Release build was clean.System.Windows.Forms.Testsrun on the final commit: 89,117 passed, 912 skipped, 72 failed (90,101 total). All 903 TableLayout cases also passed within this full run.System.Windows.Forms.dllwith the unmodified baseline build. 71 of the 72 exact failing cases also failed with the baseline; the baseline probe had 71 failures and 33 passes, with no additional failing cases. These include native language, DPI/size, font, input-language, COM/control, and related host-dependent expectations. This was a targeted baseline comparison, not a second full 90,101-test baseline run.ClipboardTests.GetApi_GetDataFailsAndSwitchEnabled_Throwsfor GetText/UnicodeText (clipboard operation failed during the full run). Rerunning that method on the final PR build passed all 5 cases. The evidence is consistent with a transient clipboard failure; the original full-run failure remains reported above.No tests were disabled by this PR. CI validation is still required.
Commands for the focused and full runs, from the PR checkout:
Performance measurements
Baseline:
f0cd8e488e17c03994589d835fb0c1c1269b4491; change:e3b3be1a3183bd7824b48c13fd367e055aa678c3. Both built in Release with the repository SDK; separate prebuilt benchmark outputs, sequential runs,DOTNET_TieredCompilation=0.These measurements use a small Stopwatch-based diagnostic harness, not BenchmarkDotNet confidence-interval results. Setup, object construction, forced GC, validation, and disposal are outside the timed region. The isolated benchmark calls delegates bound to the actual private implementation (no copied production algorithm).
Isolated drain: median microseconds for R calls to AdvanceRow.
At larger sizes, doubling the row count approaches fourfold baseline time versus twofold PR time. Timed isolated advancement allocated 0 bytes in both versions.
Public layout: median microseconds for ResumeLayout(true). Two columns, one control spanning R rows, and R flow controls.
Separate full sweeps varied noticeably; the table is one complete sweep, not a promise of those exact ratios. To check the small-case differences, a separate A/B/B/A sequence used 31 batches per size per process:
The last table averages the two per-process medians for each version; it is not a pooled median. Allocated bytes were identical before/after at each size in the measured public workloads (836,032 bytes at 4,096 rows). This does not imply unchanged allocations for every sliding-window workload.
Benchmark source and reproduction
Build the baseline and PR checkouts in Release using the repository's normal build instructions. Keep them separate. Put the following three files in a sibling benchmark directory outside either checkout. Set the two checkout paths below to their absolute local paths; the project references their built WinForms assemblies.
Build both benchmark outputs before running timing measurements. The program prints the loaded assembly path so it can be checked. Use the same host, SDK/runtime, and tiering settings for both versions; avoid other builds while measuring.
global.json(the SDK used for these measurements):{ "sdk": { "version": "11.0.100-rc.1.26420.103", "allowPrerelease": true, "rollForward": "disable" } }ReservationGridBenchmark.csproj:Program.cs:Test environment(s)
--culture en-US. Native UI culture/DPI expectations are relevant to the full-suite failures described above.Microsoft Reviewers: Open in CodeFlow