Re: [PATCH v1 2/2] mm/memory: add anonymous mTHP folios to deferred split list

Barry Song <[email protected]> Tue, 4 Aug 2026 05:20:00 +0800
Newsgroups org.kvack.linux-mm
Message-ID <CAGsJ_4x86EYsMz_n2NKDwTqeiv2pi4AcZb1RuZoBeUBR1iFqOA@mail.gmail.com>
On Mon, Aug 3, 2026 at 10:45=E2=80=AFPM Johannes Weiner <[email protected]=
> wrote:
>
> On Sat, Aug 01, 2026 at 03:33:42PM +0800, Barry Song wrote:
> > On Fri, Jul 31, 2026 at 11:04=E2=80=AFPM Johannes Weiner <hannes@cmpxch=
g.org> wrote:
> > > This is kind of tangential, but I'm curious if you would have
> > > experimented with larger folios AND the THP shrinker?
> > >
> >
> > Haven't tried it yet.
> >
> > A key difference between Android and server workloads is that Android
> > typically has many applications running with frequent
> > foreground/background transitions. When an app moves to the
> > background, much of its memory may be compressed. When it returns to
> > the foreground, it often allocates a large amount of new memory,
> > creating significant memory pressure.
> >
> > If we need around 200 ms to cold/warm start an app, allocating 2 MB
> > folios and later splitting them into smaller ones via the THP shrinker
> > could cause us to hit this shrinker path directly during the app launch
> > process. For example, we may spend the first 100 ms allocating 2 MB
> > folios and then the next 100 ms shrinking them back under memory
> > pressure. This would be quite ironic for Android :-)
> >
> > Since Android app launches can demand a large amount of memory on
> > devices with limited RAM, keeping burst allocations small is also
> > important.
>
> Makes sense, thanks for the insight.
>
> It works for many DC services because it's just a bit of extra startup
> cost, while then getting predictable THP coverage for exactly those
> areas where it makes sense, and the TLB benefits pay off over long
> service runtimes.
>
> > > In Meta, 2M thp=3Dalways without the shrinker would also not have bee=
n
> > > tolerable. It OOMed immediately on a large number of services. The
> > > shrinker *is* what allowed us to use such large folios to begin with,
> > > without the internal memory waste problem.
> >
> > My understanding is that Meta's use case is a service that is already
> > running with 2 MB pages, and later additional services start and
> > require more memory. In that case, shrinking THPs from the existing
> > service to free memory for the new services makes sense to me.
>
> It's simpler than that. We had existing services, scaled to machine
> capacity, running with basepages. When we enabled 2M pages, they
> started thrashing and OOMing from areas with poor virtual packing.
>
> The shrinker makes it possible to run with THPs enabled, period. This
> is why I'm concerned about making the search for waste less
> efficient. It would likely regress things in production immediately.
>
> > > Here is an idea: the THP shrinker will not consider anything unused
> > > that has <=3D max_ptes_none zero pages. See thp_underused(). Joanne w=
as
> > > proposing to scale this knob down relative to the folio size for mTHP
> > > shrinking. What if instead we kept the meaning absolute?
> > >
> > > The knob is an expression of how much waste the user is willing to
> > > tolerate per folio. If the folio order in question couldn't possibly
> > > have that much waste in the first place, we don't have to queue it?
> > >
> > > Something like this:
> > >
> > > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > > index 2bccb0a53a0a..1670e9869bd3 100644
> > > --- a/mm/huge_memory.c
> > > +++ b/mm/huge_memory.c
> > > @@ -4364,6 +4364,9 @@ void deferred_split_folio(struct folio *folio, =
bool partially_mapped)
> > >         if (!partially_mapped && !split_underused_thp)
> > >                 return;
> > >
> > > +       if (!partially_mapped && folio_nr_pages(folio) <=3D khugepage=
d_max_ptes_none)
> > > +               return;
> > > +
> > >         /*
> > >          * Exclude swapcache: originally to avoid a corrupt deferred =
split
> > >          * queue. Nowadays that is fully prevented by __memcg1_swapou=
t();
> > >
> > > The setting defaults to the PMD-1, so out of the box we wouldn't queu=
e
> > > any new orders. It would allow that 2MB on 64k ARM usecase, without
> > > jeopardizing smaller mTHP usecases like Barry's.
> > >
> > > Thoughts?
> >
> > My understanding is that adding smaller folios to the deferred split
> > list is not the right approach. We could end up with a very large list
> > where we cannot distinguish partially unmapped folios from fully mapped
> > ones. For example, the deferred split list could contain 100 fully mapp=
ed
> > folios but only a single partially mapped folio.
> > Moreover, for smaller large folios, the number of zero subpages
> > is likely to be small and short-lived.
> >
> > However, this is probably fine for larger large folios, since it is
> > unlikely to significantly increase the size of the deferred split
> > list. In other words, the deferred split list should remain manageable.
> > For the same reason, larger large folios may not benefit much from the
> > LRU cache either.
> >
> > So if we have some mechanism to prevent users from doing things that
> > are not beneficial, such as adding smaller large folios to the list, it
> > seems reasonable to me.
>
> Just to clarify, we're on the same page, right? I was proposing a
> mechanism to do just that.

For example, with smaller folios, the distribution might look
something like this:

Smaller large folios

+------------------------------------------------------------+
| F | F | F | F | F | F | F | F | F | F | F | F | F | P | Z |
+------------------------------------------------------------+

F: Fully mapped      (dominant)
P: Partially mapped  (rare)
Z: Zero subpages mapped (rare)

So it doesn't make much sense to add them to the list, because we
would rarely find P, and even the few Z entries we do find would
soon be filled with non-zero data anyway.

For larger folios, the list becomes much shorter. As folio size
increases, they are more likely to contain zero-filled subpages:

Larger large folios:

+------------------------------------------------------------+
| F | P | Z | F | P | Z | P | F | Z | F | P | Z | F | P | Z |
+------------------------------------------------------------+

F, P and Z become much more evenly distributed.

So if the folio is large enough, this seems acceptable. I know a
sysctl knob may not be well received, as it adds to the user's
configuration burden. Perhaps we could just hard-code a
sufficiently large value instead? for example,

#define LARGE_FOLIO_ZERO_SCAN_MIN_SIZE SZ_2M

if (folio_size(folio) >=3D LARGE_FOLIO_ZERO_SCAN_MIN_SIZE)
     deferred_split_folio(folio, false);

If, someday, people find that 1 MiB also helps, they can provide
data to support it.

>
> > As a side note (I'm not quite sure whether this is relevant to this
> > discussion), one thing we tried previously as an out-of-tree proof of
> > concept was:
> >
> > for (pfn =3D start_phys_pfn; pfn < end_phys_pfn;) {
> >      folio =3D get_folio_from_pfn();
> >      if (folio_is_zero_fill(folio))
> >             swap_out(folio);
> >      pfn +=3D folio_nr_pages();
> > }
> >
> > Then I was using my previous patch to remap it to zero-pfn if someone
> > reads it:
> > https://lore.kernel.org/linux-mm/[email protected]=
om/
> >
> > This does not depend on any list-related logic. We can scan all PFNs
> > within a short time.
>
> Hm there are 268 million PFNs on a 1TB host. I don't think that can
> scale?
>

Yes, this is a problem. We tried this on a GB-scale system, and
the scanning itself was very fast. However, it provided little
benefit, as the zero-filled subpages were quickly overwritten
with non-zero data.

> Compaction used to be linear scans, but needed to grow the freelist
> search and a whole bunch of position hinting to stay ahead of scaling
> bottlenecks.

Right. If we want a physical-address-based scan, we'll need some
lightweight mechanism to hint where scanning should start.

Thanks
Barry