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

From: Chengfeng Lin

Date: Wed Jul 01 2026 - 06:50:45 EST


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" <chengfenglin@xxxxxxxxxxxxxx>
> 发送时间:2026-06-25 01:28:04 (星期四)
> 收件人: "Pedro Falcato" <pfalcato@xxxxxxx>, "Andrew Morton" <akpm@xxxxxxxxxxxxxxxxxxxx>, linux-mm@xxxxxxxxx
> 抄送: "Liam R. Howlett" <liam@xxxxxxxxxxxxx>, "Lorenzo Stoakes" <ljs@xxxxxxxxxx>, "Vlastimil Babka" <vbabka@xxxxxxxxxx>, "Jann Horn" <jannh@xxxxxxxxxx>, linux-kernel@xxxxxxxxxxxxxxx, regressions@xxxxxxxxxxxxxxx
> 主题: [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
>