Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 425f9293f8d4f3cd2423eabd9dd099fe0dce82b2
https://github.com/WebKit/WebKit/commit/425f9293f8d4f3cd2423eabd9dd099fe0dce82b2
Author: Sammy Gill <[email protected]>
Date: 2026-01-05 (Mon, 05 Jan 2026)
Changed paths:
A PerformanceTests/Layout/subgrids-spanning-many-rows.html
M Source/WebCore/rendering/Grid.h
M Source/WebCore/rendering/GridTrackSizingAlgorithm.cpp
M Source/WebCore/rendering/GridTrackSizingAlgorithm.h
Log Message:
-----------
[Grid] Slow performance in accumulateIntrinsicSizesForTrack causes sluggish
behavior on some content.
https://bugs.webkit.org/show_bug.cgi?id=304869
rdar://problem/167456605
Reviewed by Elika Etemad and Alan Baradlay.
Almost all of the time is spent inside of
GridTrackSizingAlgorithm::accumulateIntrinsicSizesForTrack,
so in this patch I reworked this method from being a recursive
implementation that iterates over all of the tracks to being an
iterative one that just iterates over all of the grid items. To properly
explain and understand why this is an improvement I will first explain
what the code currently does. To keep things simple we will assume that
we are sizing the columns but the same exact logic applies to the rows.
In the case where the content does not have any subgrids this is
actually fairly straightforward.
GridTrackSizingAlgorithm::resolveIntrinsicTrackSizes
will iterate over each of the intrinsically sized tracks and provide a
GridIterator via GridIterator iterator(grid, GridTrackSizingDirection::Columns,
0)
to accumulateIntrinsicSizesForTrack. That method will then use the
iterator to get each of the items in column 0 by calling
iterator.nextGridItem().
The interesting bit is actually how the iterator handles looping over all
of the items in a track. When looping over the columns, the iterator
stores m_direction as Columns to find the track that we will loop over.
This loop is done by keeping track of a separate index in the opposite
dimension called the varyingTrackIndex which gets incremented to loop
over all of the tracks. So to get all of the items in column 0,
varyingTrackIndex will correspond to the index of the current row we are
looking at and will get incremented until we go through all the rows.
This means that the iterator may return the same item multiple times if
it ends up spanning multiple rows. accumulateIntrinsicTrackSizes handles
this quirk by keeping track of a separate items set and skips the item
if it is already present in the set.
Each time we get a new item from the iterator, we will then either call
sizeTrackToFitNonSpanningItem on that item or put it into one of two
other vectors that will get sorted and used later in
resolveIntrinsicTrackSizes.
However, things get slightly more complex when we start throwing
subgrids into the mix. This is because subgrids may add in an extra
layer of implicit margin to items in the track through their gaps and
any other margin, border, and padding specified on them and can be
accumulated from any nested subgrids. The code currently accomplishes
this by implementing the logic in a recursive manner and stores the
values in a LayoutUnit that is taken in as a reference to the function.
This value gets modified and reset as we progress through the call
stack.
The performance problem stems from the fact that if we see a subgrid
that spans multiple rows, we will recurse into this function for each
row that it spans. This is because we still run the logic to correctly
compute the implicit margins *before* we realize that this is a grid
item we have already seen and continue.
While there may have been a couple of different ways to fix this bug, I
opted to slightly rework this function to instead loop over the grid
items once instead of recursing into each subgrid multiple times. This
is based off the observation that when we are sizing the columns, we
should not need to care about the rows that the items span since that
should not have any bearing on the sizes that are contributed for the
columns.
In the case where the item is not a subgrid, the logic is basically the
same as before, but the only difference is that we need to figure out
the track that gets passed into sizeTrackToFitNonSpanningItem rather than the
subgrid.
than using the one that was passed in from resolveIntrinsicTrackSizes.
When we encounter a subgrid, the core logic is mostly the same, but we
need to keep track of the state on our own stack rather than relying on
the call stack to hold the state during recursion. To do this, I created
a new struct that is used locally within this function called
SizingData.
On my M4 Max MBP, the above test case goes from ~0.20 runs/s to ~50
runs/s. Other engines still show considerably higher runs/s, but this is
definitely a noticeable improvement and a good starting point to build
on.
Canonical link: https://commits.webkit.org/305121@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications