Re: [PATCH 3/4] KVM: x86/mmu: Add automatic prefaulting/prefetching for TDP MMU faults
From: Marangoni, Marco
Date: Thu Oct 29 2026 - 14:26:38 EST
Hi Sean, thanks for the patch!
I run some benchmarks on it, and it definitely helps! This is the time to fault 128 MiB linearly after a snapshot restore, with an UFFD page handler that faults chunks on demand; medians of 60 restores. Baseline is kvm-x86/next. Each entry is nr_pages: change vs baseline on the patched kernel; negative means faster.
- UFFD 4 KiB handler (baseline 2228 ms) 0: +1%, 1: −5%, 2: −3%, 4: +1%, 8: −3%, 16: +1%, 32: 0%, 64: 0%, 128: −2%, 256: +1%, 512: +1%
- UFFD 512 KiB handler (baseline 543 ms) 0: 0%, 1: 0%, 2: −36%, 4: −51%, 8: −59%, 16: −64%, 32: −71%, 64: −67%, 128: −68%, 256: −67%, 512: −66%
- UFFD 2 MiB handler (baseline 513 ms) 0: 0%, 1: +1%, 2: −34%, 4: −50%, 8: −59%, 16: −64%, 32: −68%, 64: −68%, 128: −68%, 256: −68%, 512: −68%
8 is the series' default; 0 and 1 both disable prefetch and reproduce baseline. The 4 KiB row is within ±5%, i.e. noise.
It's measured on nested virtualization, so the improvement is probably less dramatic on metal. For context, my initial patch was around ~75% faster, so I'd say that performance-wise, your implementation is solid!
It seems like a prefetch of 32 is the sweet spot, but I agree that with James that with the normal GUP it's probably better to keep it off or very low by default.
From my perspective, using fast GUP has the problem that you cannot prefault "around" your page, but just ahead of it, so I don't mind going with normal GUP if it makes things easier for you.
I'd say that most setup would know the size at which UFFD copies the memory, so having to explicitly configure is okay.
For completeness, I also measured how much the patch improves at various UFFD chunks, with nr_pages at the same size:
- 4 KiB: 2285 ms → 2321 ms (np1), +2% (np1 is "off", noise)
- 8 KiB: 1435 ms → 1441 ms (np2), 0%
- 16 KiB: 1036 ms → 860 ms (np4), −17%
- 32 KiB: 743 ms → 469 ms (np8), −37%
- 64 KiB: 645 ms → 319 ms (np16), −50%
- 128 KiB: 587 ms → 242 ms (np32), −59%
- 256 KiB: 561 ms → 200 ms (np64), −64%
- 512 KiB: 543 ms → 165 ms (np128), −70%
- 1 MiB: 534 ms → 171 ms (np256), −68%
- 2 MiB: 523 ms → 151 ms (np512), −71%
These measurement highlight a possible limitation (why didn't 8KiB improve?): it seems like the prefetching doesn't kick in the first time a page is getting faulted in the chunk, is that expected? I asked an LLM to investigate, and it seems to be because with async PF, the first fault doesn't reach the kvm_mmu_auto_prefault code path.
---
And another question: this is just for x86. Do you think the same approach could be extended for ARM, or is that a different beast? Do you need some help to make that a reality as well?
Thanks again,
Marco