Repository navigation
Reuse nested trial layouts within a render - #54
Merged
FlorianRappl merged 4 commits intoNov 12, 2026
Merged
Conversation
A trial layout is laid out on its own, so the same inputs give the same result within one render. Nested tables and inline-blocks trial-laid out every level again for each level above it, doubling the work per level (20 nested tables took 16 s, 22 over a minute). Trials inside a trial, the ones the layout around them repeats, are now kept per element with their inputs (style map, width, inherited style, measuring mode) for the render, not inside a line-clamped box; incremental rendering drops them on every change.
Without the trial cache, 6 nested tables take 63 trial layouts and 12 take 4095 (2^depth - 1).
5 of 9 tasks
FlorianRappl
requested changes
Nov 11, 2026
FlorianRappl
left a comment
Contributor
There was a problem hiding this comment.
Tests fail on MacOS. Not a snapshot issue, but rather a full crash.
Laid out only a few times, the layout stays in the first JIT tier, whose frames are large: 12 nested inline-blocks overflowed the test thread's stack on macOS. With tiered compilation off, devel and the trial cache need the same stack.
michaelthyregod
added a commit
to michaelthyregod/AngleSharp.Renderer
that referenced
this pull request
Nov 12, 2026
Each of their levels lays out the levels below it twice, so up to the depth the stack allows the test never completed in CI. They come back once the nested trial layouts are reused (AngleSharp#54).
Contributor
Author
|
Thanks! The crash is the new test: 12 nested inline-blocks need more stack in a fresh process than the macOS test thread has. With the cache the layout runs so rarely that it stays in the JIT's first tier, whose frames are about 3x larger; devel's 4095 trial layouts get it recompiled along the way. With tiered compilation off, both need the same stack. The test now counts on a thread with a 16 MB stack, so it no longer depends on the platform's thread size. |
michaelthyregod
added a commit
to michaelthyregod/AngleSharp.Renderer
that referenced
this pull request
Nov 12, 2026
…again With the nested trial layouts reused (AngleSharp#54), they render in well under a second.
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.
Types of Changes
Prerequisites
Please make sure you can check the following two boxes:
Contribution Type
What types of changes does your code introduce? Put an
xin all the boxes that apply:Description
Since 0.8.0, nested tables and inline-blocks take time doubling with every level. Each level measures its content with
TrialLayoutand then lays it out for real, so level n is laid out 2^n times: 6 nested tables take 63 trial layouts, 12 take 4095.A trial is laid out on its own (at the origin, with its own floats, positioned and sticky scopes), so the same inputs give the same result within a render. Trials started inside another trial, the ones the layout around them repeats, are now kept in the
LayoutContextfor the render, likeMeasuredHeights: per element, with their inputs (style map, containing width, inherited text style, forced width, atomic/flex item,maxY, measuring mode). Not inside a line-clamped box (its lines are counted), and incremental rendering drops them on every change. 6 nested tables now take 6 trial layouts, 12 take 12. The lookup is a method of its own, so the frame ofTrialLayout(on the stack once per nesting level) does not grow.NestedTrialLayouts_GrowLinearlyWithTheDepthchecks this for tables and inline-blocks (RenderMetrics.TrialLayouts).Benchmarks (
AngleSharp.Renderer.Benchmarks, devel 5a52786 vs this PR, P-cores, 3 alternating rounds, median):BuildDisplayList, net10.0RenderToPng, net10.0BuildDisplayList, net8.0RenderToPng, net8.0The benchmark page has few nested trials (40 of 966 are reused), so the time differences are within the spread of a single run (StdDev up to 15% of the mean for
BuildDisplayList); the kept trials cost 0.29 MB (0.5%) per render there.