* [PATCH] mm/filemap: let sequential faults restart mmap readahead
@ 2026-11-09 14:23 Alireza Haddadi via B4 Relay
2026-11-09 17:11 ` Jan Kara
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Alireza Haddadi via B4 Relay @ 2026-11-09 14:23 UTC (permalink / raw)
To: Matthew Wilcox (Oracle), Jan Kara, Andrew Morton, Vishal Moola,
fujunjie
Cc: linux-fsdevel, linux-mm, linux-kernel, Alireza Haddadi,
Roman Gushchin
From: Alireza Haddadi <alireza.haddadi@it.uu.se>
After commit 0b9c0aeba938 ("mm/filemap: count only the faulting address
as a mmap hit"), once a file's mmap_miss goes over MMAP_LOTSAMISS it can
stay there until the file is closed.
With mmap_miss over the limit, do_sync_mmap_readahead() reads only the
page that faulted. Since nothing gets read ahead, no later fault can
land on a page that readahead brought in. In turn filemap_map_pages()
never counts a hit, and the counter never comes back down. From then
on every fault on the file reads only a single page, even when the
access has become sequential.
I ran into this on v7.3 with llama.cpp, where a model larger than its
memory limit sometimes ended up being read 4 KiB at a time, but other
programs hit it as well. Anything that does some random reads and then
reads the same open file in order faces the same problem. I tested the
following:
- SQLite with mmap_size set, 300 random lookups followed by a full scan
of a 1.8 GiB table. The scan took 9.3-14.2 s instead of 2.1 s in 8 of
10 runs.
- numpy.memmap, 1000 random rows sampled from the first 75% of a 4 GiB
array and then the last 25% read sequentially, as a training and
evaluation loop would do. The sequential part took 3.3 s instead of
0.3 s in 5 of 5 runs.
Give a sequential reader a way back. When readahead is off and the page
before the faulting one is in the page cache, read the next page too
and mark it with PG_readahead. A sequential reader faults on that page
next, which starts async readahead. That path is not gated by mmap_miss,
and the pages it brings in count as hits again, so the counter drops
and readahead resumes. A random reader, however, rarely has the previous
page cached, so it keeps reading one page per fault, or at most two.
Forced THP readahead is left as it is.
This reproducer takes 100 random faults on a file of at least 4 GiB
and then reads 1 GiB of it in order:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/resource.h>
/* ./a.out FILE N: N random faults, then read 1 GiB in order */
int main(int argc, char **argv)
{
long G = 1L << 30, i, flt, size;
struct rusage ru;
volatile char *p;
int fd;
if (argc != 3 || (fd = open(argv[1], O_RDONLY)) < 0)
return 2;
size = lseek(fd, 0, SEEK_END);
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
/* N random faults beyond 3 GiB */
for (i = 0; i < atol(argv[2]); i++)
p[3 * G + random() % (size - 3 * G) / 4096 * 4096];
/* then read [2 GiB, 3 GiB) in order */
getrusage(RUSAGE_SELF, &ru);
flt = ru.ru_majflt;
for (i = 2 * G; i < 3 * G; i += 4096)
p[i];
getrusage(RUSAGE_SELF, &ru);
printf("%ld major faults for %ld pages\n",
ru.ru_majflt - flt, G / 4096);
return 0;
}
$ ./a.out FILE 100
before: 262144 major faults for 262144 pages
after: 2 major faults for 262144 pages
I also ran the access patterns from that commit's changelog (random,
strides of 1021, 1024, 2053, and 4099 pages, and sequential, on a
20 GiB and a 4 GiB file in an 8 GiB guest). With this change, bytes
read stay within 0.1% and times within 3% in all cases, so its gains
are kept.
Fixes: 0b9c0aeba938 ("mm/filemap: count only the faulting address as a mmap hit")
Signed-off-by: Alireza Haddadi <alireza.haddadi@it.uu.se>
Cc: fujunjie <fujunjie1@qq.com>
Cc: Jan Kara <jack@suse.cz>
Cc: Vishal Moola <vishal.moola@gmail.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
---
Tested on mm-new (x86-64):
- With the patch, no run gets stuck: the SQLite scan (10 runs) and
the numpy pass (5 runs) take as long as their controls.
- mm selftests: no new failures. droppable fails about once in 20
runs, with and without this patch.
- A KVM guest with KASAN, lockdep and DEBUG_VM ran the reproducer
above, strided and interleaved access, and a 4-thread test: after
200 random faults, 4 threads read the same file in order through
one mapping, so they all take the new path at once; the file is
twice the guest's memory, so this runs under reclaim. No reports,
with or without the patch.
- W=1 and sparse are clean, also with THP or LRU_GEN disabled.
The cost for random readers, once readahead is off: when every third
page is cached and each access lands just after a cached page, 1.77
pages are read per access instead of 0.89. Strided access reads the
same as before.
Forced THP readahead has a similar problem with mmap_miss. I left it
out to keep this change small and will look at it separately.
I can share the benchmark scripts (SQLite, numpy, the access patterns
of 0b9c0aeba938) if that helps.
---
mm/filemap.c | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/mm/filemap.c b/mm/filemap.c
index 93db8fc60ae0..0930aacee9fe 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3403,8 +3403,33 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
* Do we miss much more than hit in this file? If so,
* stop bothering with read-ahead. It will only hurt.
*/
- if (mmap_miss > MMAP_LOTSAMISS)
+ if (mmap_miss > MMAP_LOTSAMISS) {
+ struct folio *prev = NULL;
+
+ /*
+ * Nothing is read ahead now, so mmap_miss stays high
+ * forever. If the previous page is cached, this may be
+ * a sequential read: read the next page as well and
+ * mark it, so that async readahead, which does not
+ * check mmap_miss, restarts when the mark is hit. The
+ * pages it reads then count as hits and lower
+ * mmap_miss. A random reader rarely has the previous
+ * page cached and keeps reading one page per fault.
+ */
+ if (!force_thp_readahead && vmf->pgoff)
+ prev = xa_load(&mapping->i_pages,
+ vmf->pgoff - 1);
+ if (prev && !xa_is_value(prev)) {
+ fpin = maybe_unlock_mmap_for_io(vmf, fpin);
+ ra->start = vmf->pgoff;
+ ra->size = 2;
+ ra->async_size = 1;
+ ra->order = 0;
+ ractl._index = ra->start;
+ page_cache_ra_order(&ractl, ra);
+ }
return fpin;
+ }
}
if (force_thp_readahead) {
---
base-commit: b95f8bb86713c860ce8536d22a988b949b884d5f
change-id: 20261108-mmap-miss-bf5a501088eb
Best regards,
--
Alireza Haddadi <alireza.haddadi@it.uu.se>
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-09 14:23 [PATCH] mm/filemap: let sequential faults restart mmap readahead Alireza Haddadi via B4 Relay
@ 2026-11-09 17:11 ` Jan Kara
2026-11-09 20:23 ` Alireza Haddadi
2026-11-09 18:56 ` sashiko-bot
2026-11-11 4:23 ` Gopal Malaviya
2 siblings, 1 reply; 7+ messages in thread
From: Jan Kara @ 2026-11-09 17:11 UTC (permalink / raw)
To: alireza.haddadi
Cc: Matthew Wilcox (Oracle), Jan Kara, Andrew Morton, Vishal Moola,
fujunjie, linux-fsdevel, linux-mm, linux-kernel, Roman Gushchin
On Mon 09-11-26 15:23:24, Alireza Haddadi via B4 Relay wrote:
> From: Alireza Haddadi <alireza.haddadi@it.uu.se>
>
> After commit 0b9c0aeba938 ("mm/filemap: count only the faulting address
> as a mmap hit"), once a file's mmap_miss goes over MMAP_LOTSAMISS it can
> stay there until the file is closed.
>
> With mmap_miss over the limit, do_sync_mmap_readahead() reads only the
> page that faulted. Since nothing gets read ahead, no later fault can
> land on a page that readahead brought in. In turn filemap_map_pages()
> never counts a hit, and the counter never comes back down. From then
> on every fault on the file reads only a single page, even when the
> access has become sequential.
>
> I ran into this on v7.3 with llama.cpp, where a model larger than its
> memory limit sometimes ended up being read 4 KiB at a time, but other
> programs hit it as well. Anything that does some random reads and then
> reads the same open file in order faces the same problem. I tested the
> following:
>
> - SQLite with mmap_size set, 300 random lookups followed by a full scan
> of a 1.8 GiB table. The scan took 9.3-14.2 s instead of 2.1 s in 8 of
> 10 runs.
>
> - numpy.memmap, 1000 random rows sampled from the first 75% of a 4 GiB
> array and then the last 25% read sequentially, as a training and
> evaluation loop would do. The sequential part took 3.3 s instead of
> 0.3 s in 5 of 5 runs.
>
> Give a sequential reader a way back. When readahead is off and the page
> before the faulting one is in the page cache, read the next page too
> and mark it with PG_readahead. A sequential reader faults on that page
> next, which starts async readahead. That path is not gated by mmap_miss,
> and the pages it brings in count as hits again, so the counter drops
> and readahead resumes. A random reader, however, rarely has the previous
> page cached, so it keeps reading one page per fault, or at most two.
> Forced THP readahead is left as it is.
>
> This reproducer takes 100 random faults on a file of at least 4 GiB
> and then reads 1 GiB of it in order:
>
> #define _GNU_SOURCE
> #include <fcntl.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
> #include <sys/mman.h>
> #include <sys/resource.h>
>
> /* ./a.out FILE N: N random faults, then read 1 GiB in order */
> int main(int argc, char **argv)
> {
> long G = 1L << 30, i, flt, size;
> struct rusage ru;
> volatile char *p;
> int fd;
>
> if (argc != 3 || (fd = open(argv[1], O_RDONLY)) < 0)
> return 2;
> size = lseek(fd, 0, SEEK_END);
> posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
> p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
>
> /* N random faults beyond 3 GiB */
> for (i = 0; i < atol(argv[2]); i++)
> p[3 * G + random() % (size - 3 * G) / 4096 * 4096];
>
> /* then read [2 GiB, 3 GiB) in order */
> getrusage(RUSAGE_SELF, &ru);
> flt = ru.ru_majflt;
> for (i = 2 * G; i < 3 * G; i += 4096)
> p[i];
> getrusage(RUSAGE_SELF, &ru);
> printf("%ld major faults for %ld pages\n",
> ru.ru_majflt - flt, G / 4096);
> return 0;
> }
>
> $ ./a.out FILE 100
> before: 262144 major faults for 262144 pages
> after: 2 major faults for 262144 pages
>
> I also ran the access patterns from that commit's changelog (random,
> strides of 1021, 1024, 2053, and 4099 pages, and sequential, on a
> 20 GiB and a 4 GiB file in an 8 GiB guest). With this change, bytes
> read stay within 0.1% and times within 3% in all cases, so its gains
> are kept.
>
> Fixes: 0b9c0aeba938 ("mm/filemap: count only the faulting address as a mmap hit")
> Signed-off-by: Alireza Haddadi <alireza.haddadi@it.uu.se>
> Cc: fujunjie <fujunjie1@qq.com>
> Cc: Jan Kara <jack@suse.cz>
> Cc: Vishal Moola <vishal.moola@gmail.com>
> Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
> Cc: Roman Gushchin <roman.gushchin@linux.dev>
Hum, this looks like an ellegant way around the problem so I'm willing to
give this a try :). Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> Tested on mm-new (x86-64):
>
> - With the patch, no run gets stuck: the SQLite scan (10 runs) and
> the numpy pass (5 runs) take as long as their controls.
> - mm selftests: no new failures. droppable fails about once in 20
> runs, with and without this patch.
> - A KVM guest with KASAN, lockdep and DEBUG_VM ran the reproducer
> above, strided and interleaved access, and a 4-thread test: after
> 200 random faults, 4 threads read the same file in order through
> one mapping, so they all take the new path at once; the file is
> twice the guest's memory, so this runs under reclaim. No reports,
> with or without the patch.
> - W=1 and sparse are clean, also with THP or LRU_GEN disabled.
>
> The cost for random readers, once readahead is off: when every third
> page is cached and each access lands just after a cached page, 1.77
> pages are read per access instead of 0.89. Strided access reads the
> same as before.
>
> Forced THP readahead has a similar problem with mmap_miss. I left it
> out to keep this change small and will look at it separately.
>
> I can share the benchmark scripts (SQLite, numpy, the access patterns
> of 0b9c0aeba938) if that helps.
> ---
> mm/filemap.c | 27 ++++++++++++++++++++++++++-
> 1 file changed, 26 insertions(+), 1 deletion(-)
>
> diff --git a/mm/filemap.c b/mm/filemap.c
> index 93db8fc60ae0..0930aacee9fe 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -3403,8 +3403,33 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
> * Do we miss much more than hit in this file? If so,
> * stop bothering with read-ahead. It will only hurt.
> */
> - if (mmap_miss > MMAP_LOTSAMISS)
> + if (mmap_miss > MMAP_LOTSAMISS) {
> + struct folio *prev = NULL;
> +
> + /*
> + * Nothing is read ahead now, so mmap_miss stays high
> + * forever. If the previous page is cached, this may be
> + * a sequential read: read the next page as well and
> + * mark it, so that async readahead, which does not
> + * check mmap_miss, restarts when the mark is hit. The
> + * pages it reads then count as hits and lower
> + * mmap_miss. A random reader rarely has the previous
> + * page cached and keeps reading one page per fault.
> + */
> + if (!force_thp_readahead && vmf->pgoff)
> + prev = xa_load(&mapping->i_pages,
> + vmf->pgoff - 1);
> + if (prev && !xa_is_value(prev)) {
> + fpin = maybe_unlock_mmap_for_io(vmf, fpin);
> + ra->start = vmf->pgoff;
> + ra->size = 2;
> + ra->async_size = 1;
> + ra->order = 0;
> + ractl._index = ra->start;
> + page_cache_ra_order(&ractl, ra);
> + }
> return fpin;
> + }
> }
>
> if (force_thp_readahead) {
>
> ---
> base-commit: b95f8bb86713c860ce8536d22a988b949b884d5f
> change-id: 20261108-mmap-miss-bf5a501088eb
>
> Best regards,
> --
> Alireza Haddadi <alireza.haddadi@it.uu.se>
>
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-09 14:23 [PATCH] mm/filemap: let sequential faults restart mmap readahead Alireza Haddadi via B4 Relay
2026-11-09 17:11 ` Jan Kara
@ 2026-11-09 18:56 ` sashiko-bot
2026-11-11 4:23 ` Gopal Malaviya
2 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-09 18:56 UTC (permalink / raw)
To: Alireza Haddadi, Matthew Wilcox (Oracle), Jan Kara, Andrew Morton,
Vishal Moola, fujunjie
Cc: linux-mm, Roman Gushchin
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] mm/filemap: do_sync_mmap_readahead() fails when
mapping_min_folio_order() > 0
--
commit 6cc011b67fa27fb6f1bce2ab0a11aad698934f50
Author: Alireza Haddadi <alireza.haddadi@it.uu.se>
mm/filemap: let sequential faults restart mmap readahead
When mmap_miss exceeds MMAP_LOTSAMISS and the page preceding the faulting
offset is cached, this commit reads ahead one additional page and marks it
with PG_readahead so sequential access can restart async readahead.
> diff --git a/mm/filemap.c b/mm/filemap.c
> index 93db8fc60ae0a..0930aacee9fea 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -3403,8 +3403,33 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
> * Do we miss much more than hit in this file? If so,
> * stop bothering with read-ahead. It will only hurt.
> */
> - if (mmap_miss > MMAP_LOTSAMISS)
> + if (mmap_miss > MMAP_LOTSAMISS) {
> + struct folio *prev = NULL;
> +
> + /*
> + * Nothing is read ahead now, so mmap_miss stays high
> + * forever. If the previous page is cached, this may be
> + * a sequential read: read the next page as well and
> + * mark it, so that async readahead, which does not
> + * check mmap_miss, restarts when the mark is hit. The
> + * pages it reads then count as hits and lower
> + * mmap_miss. A random reader rarely has the previous
> + * page cached and keeps reading one page per fault.
> + */
> + if (!force_thp_readahead && vmf->pgoff)
> + prev = xa_load(&mapping->i_pages,
> + vmf->pgoff - 1);
> + if (prev && !xa_is_value(prev)) {
> + fpin = maybe_unlock_mmap_for_io(vmf, fpin);
> + ra->start = vmf->pgoff;
> + ra->size = 2;
> + ra->async_size = 1;
> + ra->order = 0;
> + ractl._index = ra->start;
> + page_cache_ra_order(&ractl, ra);
> + }
> return fpin;
> + }
> }
[Severity: Medium]
Does hardcoding ra->size = 2, ra->async_size = 1, and ra->order = 0 work on
mappings where mapping_min_folio_order(mapping) > 0, such as large block size
filesystems?
In page_cache_ra_order(), limit and mark are computed in base pages before
new_order is clamped up to min_order:
mm/readahead.c:page_cache_ra_order() {
...
if (limit > index + ra->size - 1) {
limit = index + ra->size - 1;
mark = index + ra->size - ra->async_size;
}
...
new_order = max(new_order, min_order);
}
When min_order >= 1, limit and mark both become index + 1. The allocation
loop then allocates only a single min_order folio at index, and
ra_alloc_folio() rounds mark down to 1UL << order, placing PG_readahead on
the faulting folio itself instead of a second lookahead folio.
On the FAULT_FLAG_TRIED retry after maybe_unlock_mmap_for_io(),
do_async_mmap_readahead() is skipped and finish_fault() maps all pages of
that single folio at once, so async readahead never starts and mmap_miss
stays above MMAP_LOTSAMISS. Scaling ra->size and ra->async_size by
mapping_min_folio_nrpages(mapping) and setting ra->order to
mapping_min_folio_order(mapping) would read two minimum-order folios and
mark the second one.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261109-mmap-miss-v1-1-8b44b33f3358@it.uu.se?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-09 17:11 ` Jan Kara
@ 2026-11-09 20:23 ` Alireza Haddadi
2026-11-10 10:37 ` Jan Kara
0 siblings, 1 reply; 7+ messages in thread
From: Alireza Haddadi @ 2026-11-09 20:23 UTC (permalink / raw)
To: Jan Kara
Cc: Matthew Wilcox (Oracle), Andrew Morton, Vishal Moola, fujunjie,
linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, Roman Gushchin
> On 9 Nov 2026, at 18:11, Jan Kara <jack@suse.cz> wrote:
>
> On Mon 09-11-26 15:23:24, Alireza Haddadi via B4 Relay wrote:
>> From: Alireza Haddadi <alireza.haddadi@it.uu.se>
>>
>> After commit 0b9c0aeba938 ("mm/filemap: count only the faulting address
>> as a mmap hit"), once a file's mmap_miss goes over MMAP_LOTSAMISS it can
>> stay there until the file is closed.
>>
>> With mmap_miss over the limit, do_sync_mmap_readahead() reads only the
>> page that faulted. Since nothing gets read ahead, no later fault can
>> land on a page that readahead brought in. In turn filemap_map_pages()
>> never counts a hit, and the counter never comes back down. From then
>> on every fault on the file reads only a single page, even when the
>> access has become sequential.
>>
>> I ran into this on v7.3 with llama.cpp, where a model larger than its
>> memory limit sometimes ended up being read 4 KiB at a time, but other
>> programs hit it as well. Anything that does some random reads and then
>> reads the same open file in order faces the same problem. I tested the
>> following:
>>
>> - SQLite with mmap_size set, 300 random lookups followed by a full scan
>> of a 1.8 GiB table. The scan took 9.3-14.2 s instead of 2.1 s in 8 of
>> 10 runs.
>>
>> - numpy.memmap, 1000 random rows sampled from the first 75% of a 4 GiB
>> array and then the last 25% read sequentially, as a training and
>> evaluation loop would do. The sequential part took 3.3 s instead of
>> 0.3 s in 5 of 5 runs.
>>
>> Give a sequential reader a way back. When readahead is off and the page
>> before the faulting one is in the page cache, read the next page too
>> and mark it with PG_readahead. A sequential reader faults on that page
>> next, which starts async readahead. That path is not gated by mmap_miss,
>> and the pages it brings in count as hits again, so the counter drops
>> and readahead resumes. A random reader, however, rarely has the previous
>> page cached, so it keeps reading one page per fault, or at most two.
>> Forced THP readahead is left as it is.
>>
>> This reproducer takes 100 random faults on a file of at least 4 GiB
>> and then reads 1 GiB of it in order:
>>
>> #define _GNU_SOURCE
>> #include <fcntl.h>
>> #include <stdio.h>
>> #include <stdlib.h>
>> #include <unistd.h>
>> #include <sys/mman.h>
>> #include <sys/resource.h>
>>
>> /* ./a.out FILE N: N random faults, then read 1 GiB in order */
>> int main(int argc, char **argv)
>> {
>> long G = 1L << 30, i, flt, size;
>> struct rusage ru;
>> volatile char *p;
>> int fd;
>>
>> if (argc != 3 || (fd = open(argv[1], O_RDONLY)) < 0)
>> return 2;
>> size = lseek(fd, 0, SEEK_END);
>> posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
>> p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
>>
>> /* N random faults beyond 3 GiB */
>> for (i = 0; i < atol(argv[2]); i++)
>> p[3 * G + random() % (size - 3 * G) / 4096 * 4096];
>>
>> /* then read [2 GiB, 3 GiB) in order */
>> getrusage(RUSAGE_SELF, &ru);
>> flt = ru.ru_majflt;
>> for (i = 2 * G; i < 3 * G; i += 4096)
>> p[i];
>> getrusage(RUSAGE_SELF, &ru);
>> printf("%ld major faults for %ld pages\n",
>> ru.ru_majflt - flt, G / 4096);
>> return 0;
>> }
>>
>> $ ./a.out FILE 100
>> before: 262144 major faults for 262144 pages
>> after: 2 major faults for 262144 pages
>>
>> I also ran the access patterns from that commit's changelog (random,
>> strides of 1021, 1024, 2053, and 4099 pages, and sequential, on a
>> 20 GiB and a 4 GiB file in an 8 GiB guest). With this change, bytes
>> read stay within 0.1% and times within 3% in all cases, so its gains
>> are kept.
>>
>> Fixes: 0b9c0aeba938 ("mm/filemap: count only the faulting address as a mmap hit")
>> Signed-off-by: Alireza Haddadi <alireza.haddadi@it.uu.se>
>> Cc: fujunjie <fujunjie1@qq.com>
>> Cc: Jan Kara <jack@suse.cz>
>> Cc: Vishal Moola <vishal.moola@gmail.com>
>> Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
>> Cc: Roman Gushchin <roman.gushchin@linux.dev>
>
> Hum, this looks like an ellegant way around the problem so I'm willing to
> give this a try :). Feel free to add:
>
> Reviewed-by: Jan Kara <jack@suse.cz>
>
> Honza
>
Thanks a lot for the review!
As Sashiko noticed, with a minimum folio order (large block size
filesystems), the 2-page request ends up as a single folio with the
mark on the faulting folio itself. That folio gets mapped in full on
the retry, so the mark is never hit and the readahead does not
restart there.
I checked it myself, and it's right. In v2 I'd read two minimum-size
folios instead and mark the second one:
unsigned int nr = mapping_min_folio_nrpages(mapping);
ra->size = 2 * nr;
ra->async_size = nr;
ra->order = mapping_min_folio_order(mapping);
With a minimum order of 0, this is the same as now. The cost stays the
same in proportion: a miss that follows a cached page reads two
minimum-size folios instead of one. I'll also test it on XFS with 16k
blocks before sending v2. Would you be OK with me keeping your
Reviewed-by for that, or would you rather look at v2 first?
Best regards,
Alireza
>> ---
>> Tested on mm-new (x86-64):
>>
>> - With the patch, no run gets stuck: the SQLite scan (10 runs) and
>> the numpy pass (5 runs) take as long as their controls.
>> - mm selftests: no new failures. droppable fails about once in 20
>> runs, with and without this patch.
>> - A KVM guest with KASAN, lockdep and DEBUG_VM ran the reproducer
>> above, strided and interleaved access, and a 4-thread test: after
>> 200 random faults, 4 threads read the same file in order through
>> one mapping, so they all take the new path at once; the file is
>> twice the guest's memory, so this runs under reclaim. No reports,
>> with or without the patch.
>> - W=1 and sparse are clean, also with THP or LRU_GEN disabled.
>>
>> The cost for random readers, once readahead is off: when every third
>> page is cached and each access lands just after a cached page, 1.77
>> pages are read per access instead of 0.89. Strided access reads the
>> same as before.
>>
>> Forced THP readahead has a similar problem with mmap_miss. I left it
>> out to keep this change small and will look at it separately.
>>
>> I can share the benchmark scripts (SQLite, numpy, the access patterns
>> of 0b9c0aeba938) if that helps.
>> ---
>> mm/filemap.c | 27 ++++++++++++++++++++++++++-
>> 1 file changed, 26 insertions(+), 1 deletion(-)
>>
>> diff --git a/mm/filemap.c b/mm/filemap.c
>> index 93db8fc60ae0..0930aacee9fe 100644
>> --- a/mm/filemap.c
>> +++ b/mm/filemap.c
>> @@ -3403,8 +3403,33 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
>> * Do we miss much more than hit in this file? If so,
>> * stop bothering with read-ahead. It will only hurt.
>> */
>> - if (mmap_miss > MMAP_LOTSAMISS)
>> + if (mmap_miss > MMAP_LOTSAMISS) {
>> + struct folio *prev = NULL;
>> +
>> + /*
>> + * Nothing is read ahead now, so mmap_miss stays high
>> + * forever. If the previous page is cached, this may be
>> + * a sequential read: read the next page as well and
>> + * mark it, so that async readahead, which does not
>> + * check mmap_miss, restarts when the mark is hit. The
>> + * pages it reads then count as hits and lower
>> + * mmap_miss. A random reader rarely has the previous
>> + * page cached and keeps reading one page per fault.
>> + */
>> + if (!force_thp_readahead && vmf->pgoff)
>> + prev = xa_load(&mapping->i_pages,
>> + vmf->pgoff - 1);
>> + if (prev && !xa_is_value(prev)) {
>> + fpin = maybe_unlock_mmap_for_io(vmf, fpin);
>> + ra->start = vmf->pgoff;
>> + ra->size = 2;
>> + ra->async_size = 1;
>> + ra->order = 0;
>> + ractl._index = ra->start;
>> + page_cache_ra_order(&ractl, ra);
>> + }
>> return fpin;
>> + }
>> }
>>
>> if (force_thp_readahead) {
>>
>> ---
>> base-commit: b95f8bb86713c860ce8536d22a988b949b884d5f
>> change-id: 20261108-mmap-miss-bf5a501088eb
>>
>> Best regards,
>> --
>> Alireza Haddadi <alireza.haddadi@it.uu.se>
>>
>>
> --
> Jan Kara <jack@suse.com>
> SUSE Labs, CR
>
> VARNING: Klicka inte på länkar och öppna inte bilagor om du inte känner igen avsändaren och vet att innehållet är säkert.
> CAUTION: Do not click on links or open attachments unless you recognise the sender and know the content is safe.
När du har kontakt med oss på Uppsala universitet med e-post så innebär det att vi behandlar dina personuppgifter. För att läsa mer om hur vi gör det kan du läsa här: http://www.uu.se/om-uu/dataskydd-personuppgifter/
E-mailing Uppsala University means that we will process your personal data. For more information on how this is performed, please read here: http://www.uu.se/en/about-uu/data-protection-policy
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-09 20:23 ` Alireza Haddadi
@ 2026-11-10 10:37 ` Jan Kara
0 siblings, 0 replies; 7+ messages in thread
From: Jan Kara @ 2026-11-10 10:37 UTC (permalink / raw)
To: Alireza Haddadi
Cc: Jan Kara, Matthew Wilcox (Oracle), Andrew Morton, Vishal Moola,
fujunjie, linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, Roman Gushchin
On Mon 09-11-26 20:23:29, Alireza Haddadi wrote:
> As Sashiko noticed, with a minimum folio order (large block size
> filesystems), the 2-page request ends up as a single folio with the
> mark on the faulting folio itself. That folio gets mapped in full on
> the retry, so the mark is never hit and the readahead does not
> restart there.
>
> I checked it myself, and it's right. In v2 I'd read two minimum-size
> folios instead and mark the second one:
>
> unsigned int nr = mapping_min_folio_nrpages(mapping);
> ra->size = 2 * nr;
> ra->async_size = nr;
> ra->order = mapping_min_folio_order(mapping);
>
> With a minimum order of 0, this is the same as now. The cost stays the
> same in proportion: a miss that follows a cached page reads two
> minimum-size folios instead of one. I'll also test it on XFS with 16k
> blocks before sending v2. Would you be OK with me keeping your
> Reviewed-by for that, or would you rather look at v2 first?
Yes, I agree with this solution so feel free to keep my Reviewed-by.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-09 14:23 [PATCH] mm/filemap: let sequential faults restart mmap readahead Alireza Haddadi via B4 Relay
2026-11-09 17:11 ` Jan Kara
2026-11-09 18:56 ` sashiko-bot
@ 2026-11-11 4:23 ` Gopal Malaviya
2026-11-11 7:43 ` Alireza Haddadi
2 siblings, 1 reply; 7+ messages in thread
From: Gopal Malaviya @ 2026-11-11 4:23 UTC (permalink / raw)
To: alireza.haddadi
Cc: jack, willy, akpm, vishal.moola, fujunjie1, roman.gushchin,
linux-mm, linux-fsdevel, linux-kernel, regressions, tracekotae
Hi Alireza,
I had sent a different fix for the same regression [1], reported by
Trace in [2]. Jan prefers yours, so I have dropped mine. I ran your
patch, also with the v2 change you describe to Jan, through the tests
I had: a KVM guest (x86_64, 2 vCPUs, 3 GiB), mm-hotfixes-unstable as
of 2026-11-08, ext4 and XFS with 16 KiB blocks on virtio-blk,
read_ahead_kb 128 unless noted.
The reproducer from [2] (2000 single-page faults 1 MiB apart, then a
64 MiB sequential read), major faults in the sequential part:
base as posted with v2 change
ext4 16384 2 2
XFS, 16 KiB blocks 4096 4096 2
Touching 1% of the pages of a cold 20 GiB file, larger than memory, in
random runs of 1, 2 or 4 adjacent pages, MiB read from disk with the
v2 change (first of two runs; the second was within 10%):
read_ahead_kb 128 read_ahead_kb 8192
base with v2 base with v2
random 259 234 1057 1070
stride 2053 pages 271 236 992 992
random 2-page runs 257 373 1135 2538
random 4-page runs 240 1146 1200 57748
The 4-page runs at read_ahead_kb 8192, which is what this guest's
virtio-blk disk gets by default, read about 48 times more. As far as I
can tell, each run now has two misses and up to two hits on the new
window, so mmap_miss stays around MMAP_LOTSAMISS, and each time it is
back at the limit the next random miss gets the full 8 MiB
read-around. My patch ran into the same and kept mmap_miss at its cap
on that path; something like that may help here, but you and Jan know
this better.
On the Fixes tag: with the commits of the series built one by one,
commit 0b9c0aeba938 ("mm/filemap: count only the faulting address as
a mmap hit") alone does not show the problem (1 major fault with the
reproducer), and commit 9b0fcac3cfe7 ("mm/filemap: do not count
FAULT_FLAG_TRIED retries as mmap hits") does (16384). [2] is the same
regression, in case you want to add Reported-by and Closes tags for
regzbot.
I am happy to run these again on your v2. The tests were set up with
the help of an AI assistant.
[1] https://lore.kernel.org/all/20261109091444.128521-1-gopalmalaviya53@gmail.com/
[2] https://lore.kernel.org/all/20261105192517.228523-1-tracekotae@gmail.com/
Thanks,
Gopal
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/filemap: let sequential faults restart mmap readahead
2026-11-11 4:23 ` Gopal Malaviya
@ 2026-11-11 7:43 ` Alireza Haddadi
0 siblings, 0 replies; 7+ messages in thread
From: Alireza Haddadi @ 2026-11-11 7:43 UTC (permalink / raw)
To: Gopal Malaviya
Cc: jack@suse.cz, willy@infradead.org, akpm@linux-foundation.org,
vishal.moola@gmail.com, fujunjie1@qq.com,
roman.gushchin@linux.dev, linux-mm@kvack.org,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
regressions@lists.linux.dev, tracekotae@gmail.com
> On 11 Nov 2026, at 05:23, Gopal Malaviya <gopalmalaviya53@gmail.com> wrote:
>
Hi Gopal,
Thanks for testing this so thoroughly,
and for pointing me to Trace’s report.
> Hi Alireza,
>
> I had sent a different fix for the same regression [1], reported by
> Trace in [2]. Jan prefers yours, so I have dropped mine. I ran your
> patch, also with the v2 change you describe to Jan, through the tests
> I had: a KVM guest (x86_64, 2 vCPUs, 3 GiB), mm-hotfixes-unstable as
> of 2026-11-08, ext4 and XFS with 16 KiB blocks on virtio-blk,
> read_ahead_kb 128 unless noted.
>
> The reproducer from [2] (2000 single-page faults 1 MiB apart, then a
> 64 MiB sequential read), major faults in the sequential part:
>
> base as posted with v2 change
> ext4 16384 2 2
> XFS, 16 KiB blocks 4096 4096 2
>
That matches what I see here: on mm-new without the patch, XFS with
16k blocks take one fault per 16 KiB folio in the in-order read.
> Touching 1% of the pages of a cold 20 GiB file, larger than memory, in
> random runs of 1, 2 or 4 adjacent pages, MiB read from disk with the
> v2 change (first of two runs; the second was within 10%):
>
> read_ahead_kb 128 read_ahead_kb 8192
> base with v2 base with v2
> random 4-page runs 240 1146 1200 57748
>
> The 4-page runs at read_ahead_kb 8192, which is what this guest's
> virtio-blk disk gets by default, read about 48 times more. As far as I
> can tell, each run now has two misses and up to two hits on the new
> window, so mmap_miss stays around MMAP_LOTSAMISS, and each time it is
> back at the limit the next random miss gets the full 8 MiB
> read-around. My patch ran into the same and kept mmap_miss at its cap
> on that path; something like that may help here, but you and Jan know
> this better.
This is a real problem, thanks for catching it. None of my tests had
short sequential runs at random places, so I missed it. I’ll reproduce
it here and rework the patch before sending v2. I want to compare
keeping mmap_miss high on that path, as you did, with only restarting
readahead once the fault continues a longer run of cached pages.
Jan thanks for being OK with the min-order change. Given this, v2
will change more than, so I won’t carry your Reviewed-by over.
I’d be glad if you could take another look at v2.
>
> On the Fixes tag: with the commits of the series built one by one,
> commit 0b9c0aeba938 ("mm/filemap: count only the faulting address as
> a mmap hit") alone does not show the problem (1 major fault with the
> reproducer), and commit 9b0fcac3cfe7 ("mm/filemap: do not count
> FAULT_FLAG_TRIED retries as mmap hits") does (16384). [2] is the same
> regression, in case you want to add Reported-by and Closes tags for
> regzbot.
>
I only tested reverting both commits together, so I’ll check this and fix the
tag in v2, and add Reported-by and Closes for Trace’s report.
> I am happy to run these again on your v2. The tests were set up with
> the help of an AI assistant.
That would be great, thank you. Could you share the short-run test,
So I can run the same here while working on it?
Best Regards,
Alireza
När du har kontakt med oss på Uppsala universitet med e-post så innebär det att vi behandlar dina personuppgifter. För att läsa mer om hur vi gör det kan du läsa här: http://www.uu.se/om-uu/dataskydd-personuppgifter/
E-mailing Uppsala University means that we will process your personal data. For more information on how this is performed, please read here: http://www.uu.se/en/about-uu/data-protection-policy
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-11 7:44 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 14:23 [PATCH] mm/filemap: let sequential faults restart mmap readahead Alireza Haddadi via B4 Relay
2026-11-09 17:11 ` Jan Kara
2026-11-09 20:23 ` Alireza Haddadi
2026-11-10 10:37 ` Jan Kara
2026-11-09 18:56 ` sashiko-bot
2026-11-11 4:23 ` Gopal Malaviya
2026-11-11 7:43 ` Alireza Haddadi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox