Re: [REGRESSION] mm/mprotect: shared-dirty base-page toggle slower since v6.17

"Chengfeng Lin" <[email protected]> Wed, 1 Jul 2026 18:50:20 +0800 (GMT+08:00)
Newsgroups dev.linux.lists.regressions,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <[email protected]>
Hi,

I wanted to add one small follow-up to make the scope clearer.

The original reproducer repeatedly does:

  mprotect(PROT_READ)
  mprotect(PROT_READ | PROT_WRITE)
  write-touch

I also checked whether the signal only comes from that repeated
protect/restore loop.  In the follow-up test, each timed iteration creates a
fresh 64 MiB shared-dirty mapping, checks that it is still 4 KiB/no THP, and
then times exactly one full-range:

  mprotect(PROT_READ)

This is still the same narrow shared-dirty base-page workload, not a separate
regression claim.

I also checked a MADV_HUGEPAGE control on the current kernel.  With the default
shmem THP policy, the shared mapping did not actually form THP.  After
temporarily switching shmem THP to `advise`, the same shared mapping did form
THP and the single mprotect(PROT_READ) cost was much lower.  So the numbers
below are intentionally for the 4 KiB base-page shape.

The bare-metal result is:

  kernel                         single_protect_ns_per_page
  v6.16                          8  8  8       mean 8.000
  v6.17                          14 14 14      mean 14.000
  v7.1                           18 15 18      mean 17.000

All steps reported `expected_match_ratio=100` and `unexpected_results=0`.

So a single `mprotect(PROT_READ)` on the prepared shared-dirty range already
shows the v6.16 -> v6.17 slowdown.  The earlier toggle result is therefore not
only measuring steady-state restore/write-touch effects.

Follow-up evidence:

  https://github.com/lcf0399/linux-regression-evidence/tree/8a19614b08d8d2a4c2698c1cfac5a747f12aa021/mprotect-shared-dirty-toggle/bare-metal/20260630-single-protect-followup

Updated evidence root:

  https://github.com/lcf0399/linux-regression-evidence/tree/8a19614b08d8d2a4c2698c1cfac5a747f12aa021/mprotect-shared-dirty-toggle

If this scope looks useful, I can provide more runs or narrower measurements
around the v6.16..v6.17 window.

Thanks,
Chengfeng


> -----原始邮件-----
> 发件人: "Chengfeng Lin" <[email protected]>
> 发送时间:2026-06-25 01:28:04 (星期四)
> 收件人: "Pedro Falcato" <[email protected]>, "Andrew Morton" <[email protected]>, [email protected]
> 抄送: "Liam R. Howlett" <[email protected]>, "Lorenzo Stoakes" <[email protected]>, "Vlastimil Babka" <[email protected]>, "Jann Horn" <[email protected]>, [email protected], [email protected]
> 主题: [REGRESSION] mm/mprotect: shared-dirty base-page toggle slower since v6.17
> 
> Hi,
> 
> I have a refreshed bare-metal result for the shared-dirty mprotect()
> slowdown I reported earlier from QEMU/lab testing.
> 
> The reproducer is intentionally narrow:
> 
>   - MAP_SHARED | MAP_ANONYMOUS mapping
>   - 64 MiB range, write-prefaulted before timing
>   - state check: 4 KiB base pages, no THP backing
>   - repeated full-range mprotect(PROT_READ)
>   - restore with mprotect(PROT_READ | PROT_WRITE)
>   - write-touch after each protect/restore cycle
> 
> So this is not a generic mprotect() regression claim.  The scope is the
> shared-dirty base-page PTE permission-change path.
> 
> The bare-metal machine is an Intel Core i7-14700 system.  The workload is
> single-threaded and pinned to one logical CPU with `taskset -c 2`.  The primary
> metric is `iteration_ns_per_page`, lower is better.  It is the wall-clock time
> for one full protect/restore/write-touch iteration, divided by the number of
> 4 KiB pages in the range.  Each benchmark step used 9 external rounds, 1000
> iterations, and 10 warmup iterations.
> 
> First, the v6.12 -> v6.19 result still reproduces on bare metal:
> 
>   kernel                         iteration_ns_per_page
>   v6.12.77                       26
>   v6.19.9                        37
> 
> I then narrowed the release window with 3 interleaved boot/run steps per
> kernel:
> 
>   kernel                         values          mean
>   v6.16                          25 25 25        25.000
>   v6.17                          37 37 37        37.000
>   v6.18                          38 38 38        38.000
>   v6.18.19                       38 38 38        38.000
>   v6.19.9                        37 36 37        36.667
> 
> I also checked later context with the same standalone command:
> 
>   kernel                         values          mean
>   v7.0.9                         36 36 36        36.000
>   v6.19.9 + Pedro v3 patch-only  39 39 39        39.000
>   v7.1.0-rc3 mm-unstable/Pedro   39 39 39        39.000
> 
> I do not treat the mm-unstable result as a clean release-kernel comparison.
> It is only a follow-up check, and in this workload it did not improve the
> standalone result.
> 
> All of these runs reported `expected_match_ratio=100` and
> `unexpected_results=0`.  The state check in the standalone output stays in the
> same shape: 4 KiB pages, no THP.
> 
> This puts the slowdown in the v6.16 -> v6.17 release window.
> 
> As an attribution check, I also built a v6.17 probe kernel that only changes
> the present-PTE path in `mm/mprotect.c::change_pte_range()` for this workload
> back to a single-PTE start/commit/flush shape.  That is not an upstream patch
> and not a clean release-kernel comparison; it is only a hot-path probe.
> 
> The result was:
> 
>   kernel                         values          mean
>   v6.16                          25 25 25        25.000
>   v6.17                          37 37 37        37.000
>   v6.17 single-PTE probe         25 25 25        25.000
> 
> So the targeted probe brings v6.17 back to the v6.16 range for this workload.
> That points at the v6.17 PTE-batching shape in `change_pte_range()` as the
> main cost for this shared-dirty 4 KiB base-page case.
> 
> I do not want to overstate the attribution.  I tried reversing the official
> `cac1db8c3aad ("mm: optimize mprotect() by PTE batching")` patch onto my
> linux-6.17 tree, but it did not apply cleanly.  That means this is not an
> exact revert result.  I can only say that the slowdown appears in the
> v6.16 -> v6.17 window, and that this focused probe brings the v6.17 result
> back to the v6.16 range.
> 
> Evidence bundle:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/tree/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle
> 
> Standalone reproducer:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/tree/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle/reproducer
> 
> For each installed kernel, the standalone reproducer was run as:
> 
>   taskset -c 2 env MAPPING_MB=64 ITERATIONS=1000 WARMUP=10 \
>     EXTERNAL_ROUNDS=9 ./run_mprotect_shared_dirty_reproducer.sh
> 
> For the release-window check, a small systemd/GRUB queue booted each target
> kernel before running the same command.
> 
> Bare-metal summaries and raw run logs:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/tree/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle/bare-metal
> 
> Release-window narrowing:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/tree/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle/bare-metal/20260623-narrow-6.16-6.19-3rounds
> 
> v6.17 single-PTE probe:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/tree/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle/bare-metal/20260624-6.17-singlepte-probe
> 
> Probe patch used for that attribution run:
> 
>   https://github.com/lcf0399/linux-mm-regression-evidence/blob/acd7fef0e0276ac361971b0960e6611811edf5b3/mprotect-shared-dirty-toggle/bare-metal/20260624-6.17-singlepte-probe/0001-mm-mprotect-probe-6.17-single-pte-hotpath.patch
> 
> #regzbot introduced: v6.16..v6.17
> 
> Does this scope look useful to investigate further?  If yes, I can try a more
> exact commit-level check or test a patch you think is the right direction.
> 
> Thanks,
> Chengfeng
>