[PATCH 0/2] mm/filemap: read ahead for mmap readers that move backwards

From: Qiliang Yuan

Date: Thu Oct 22 2026 - 10:01:11 EST


A reader that walks a file mapping towards lower offsets gets almost no
readahead from mmap faults when the mapping asks for it. glibc's
memmove() and memcpy() copy backwards when the destination lies above
the source, so this happens to loaders that copy large files out of a
mapping, such as LLM inference engines copying model weights out of a
safetensors mapping. Both MADV_HUGEPAGE and MADV_SEQUENTIAL read ahead
only towards higher offsets, so each fault of such a reader reads just
the part it is missing.

MADV_SEQUENTIAL was slow for this before 7.2 as well. MADV_HUGEPAGE
became much slower in 7.2: since commit 0b9c0aeba938 ("mm/filemap:
count only the faulting address as a mmap hit"), the PTEs mapped around
a fault no longer count as hits and PMD mappings never did, so a
backward reader's mmap_miss passes MMAP_LOTSAMISS after about a hundred
folios and readahead stops. Each page after that is read on its own.

Patch 1 fixes that regression: on a descending VM_HUGEPAGE fault it
also reads the folio below, and it counts a PMD mapping of the faulting
address as a hit, as a PTE mapping is. Patch 2 reads the window that
ends at the fault on a descending VM_SEQ_READ fault.

Reading one byte from each page of a 2 GiB file, last page first, with
a cold page cache, read_ahead_kb 8192, in a 32 vCPU VM:

7be4bc52fd9e patched
MADV_HUGEPAGE 36.76 s 0.41 s
MADV_SEQUENTIAL 50.28 s 0.76 s

The same walk takes 0.53 s with MADV_HUGEPAGE just before
0b9c0aeba938.

Forward walks take the same time. Megabytes read and major faults for
other access patterns over a 16 GiB file:

7be4bc52fd9e patched
1% random, MADV_NORMAL 1002 MB, 39619 1004 MB, 39619
1% random, MADV_HUGEPAGE 1583 MB, 38762 1603 MB, 38758
1% stride 64 pages 10496 MB, 4 10496 MB, 4
5% random in 2 GiB memcg,
MADV_HUGEPAGE 2203 MB, 187413 2221 MB, 187399

The walk above, run as "revwalk FILE" for MADV_HUGEPAGE and "revwalk FILE
seq" for MADV_SEQUENTIAL, is below. I can turn it into a selftest if that
is wanted.

/* revwalk FILE [seq]: read each page of FILE, last page first */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char **argv)
{
long ps = sysconf(_SC_PAGESIZE);
int fd = open(argv[1], O_RDONLY);
struct rusage ru;
struct stat st;
size_t i;
char *p;

fstat(fd, &st);
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
madvise(p, st.st_size, argc > 2 && !strcmp(argv[2], "seq") ?
MADV_SEQUENTIAL : MADV_HUGEPAGE);
for (i = st.st_size / ps; i > 0; i--)
(void)*(volatile char *)(p + (i - 1) * ps);
getrusage(RUSAGE_SELF, &ru);
printf("major faults: %ld\n", ru.ru_majflt);
return 0;
}

Signed-off-by: Qiliang Yuan <odys.yuan@xxxxxxxxx>
---
Qiliang Yuan (2):
mm/filemap: read ahead downwards on descending VM_HUGEPAGE faults
mm/filemap: extend VM_SEQ_READ readahead below descending faults

mm/filemap.c | 115 +++++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 85 insertions(+), 30 deletions(-)
---
base-commit: 7be4bc52fd9e0e55dc9a2e0232b89427d2c4b614
change-id: 20261022-bug-mm-mmap-readahead-descending-9cab86703bbb

Best regards,
--
Qiliang Yuan <odys.yuan@xxxxxxxxx>