From: Qiliang Yuan <odys.yuan@gmail.com>
To: "Matthew Wilcox (Oracle)" <willy@infradead.org>,
Jan Kara <jack@suse.cz>,
Andrew Morton <akpm@linux-foundation.org>
Cc: Joanne Koong <joannelkoong@gmail.com>,
linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, Qiliang Yuan <odys.yuan@gmail.com>
Subject: [PATCH] mm/readahead: look two windows ahead for the hole on a marker hit
Date: Thu, 22 Oct 2026 23:24:33 +0800 [thread overview]
Message-ID: <20261022-bug-mm-readahead-async-overtaken-v1-1-c05b8d8bd2da@gmail.com> (raw)
When a reader hits a folio marked for readahead that the readahead
state does not expect, page_cache_async_ra() starts the next readahead
at the first hole within one window after the folio. If that window is
fully cached, it reads nothing.
memcpy() in glibc loads the end of a large source before copying it
forward, so a loader that copies a file mapping out in pieces of a few
megabytes faults at the end of each piece first. mmap read-around then
reads a window centered on that fault, which reaches more than one
window past the readahead marker the copy has yet to hit. When the copy
reaches the marker, there is no hole within a window, no readahead is
issued, and the next piece faults on its end again. Every piece then
gets a synchronous read-around of order-0 folios, and threads copying
parts of one file also contend on the i_pages lock to insert them. A
copy from offset 0 avoids this as its first read-around is clamped to
the start of the file.
Look up to two windows ahead for the hole, which reaches past such a
read-around, so the reader keeps an asynchronous window and its folio
order keeps ramping up.
Copying a 16 GiB file out of a private mapping with memcpy() in 8 MiB
pieces, read_ahead_kb 8192, cold page cache, 32 vCPU VM, mean of two
runs:
before after
1 thread from offset 2 GiB 7.62 s 2.00 s
8 threads, one range each 3.26 s 1.08 s
32 threads, one range each 4.05 s 1.34 s
Random touches read the same amount, and pread() copies and copies that
interleave 7 MiB chunks across threads take the same time within
run-to-run noise.
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
The program below copies FILE out of a mapping in 8 MiB pieces, each
thread taking one range. With a cold page cache, run as "pcopy FILE 1
2048" (one thread starting at 2 GiB) and "pcopy FILE 8" on a 16 GiB file:
7be4bc52fd9e patched
1 thread from 2 GiB 6.92 s 1.57 s
8 threads 4.00 s 1.04 s
/* pcopy FILE THREADS [SKIP_MB]: copy FILE out of a mapping */
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#define PIECE (8UL << 20)
static char *map;
static size_t skip, per;
static void *copy(void *arg)
{
size_t off = skip + (long)arg * per, end = off + per;
char *buf = malloc(PIECE);
for (; off < end; off += PIECE) {
memcpy(buf, map + off, PIECE);
asm volatile("" : : "r" (buf) : "memory");
}
free(buf);
return NULL;
}
int main(int argc, char **argv)
{
int fd = open(argv[1], O_RDONLY), n = atoi(argv[2]);
struct timespec t0, t1;
pthread_t th[64];
struct stat st;
fstat(fd, &st);
skip = argc > 3 ? atol(argv[3]) << 20 : 0;
per = (st.st_size - skip) / n / PIECE * PIECE;
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
clock_gettime(CLOCK_MONOTONIC, &t0);
for (long i = 0; i < n; i++)
pthread_create(&th[i], NULL, copy, (void *)i);
for (int i = 0; i < n; i++)
pthread_join(th[i], NULL);
clock_gettime(CLOCK_MONOTONIC, &t1);
printf("%.2f s\n", t1.tv_sec - t0.tv_sec + (t1.tv_nsec - t0.tv_nsec) / 1e9);
return 0;
}
---
mm/readahead.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/mm/readahead.c b/mm/readahead.c
index 6e5563290287e..d7a511a9fdcbf 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -696,12 +696,15 @@ void page_cache_async_ra(struct readahead_control *ractl,
* E.g. interleaved reads.
* Query the pagecache for async_size, which normally equals to
* readahead size. Ramp it up and use it as the new readahead size.
+ * Look up to two windows ahead: read-around for a fault further on,
+ * such as from a memcpy() that touches the end of its source first,
+ * may already have filled the next one.
*/
rcu_read_lock();
- start = page_cache_next_miss(ractl->mapping, index + 1, max_pages);
+ start = page_cache_next_miss(ractl->mapping, index + 1, 2 * max_pages);
rcu_read_unlock();
- if (!start || start - index > max_pages)
+ if (!start || start - index > 2 * max_pages)
return;
ra->start = start;
---
base-commit: 7be4bc52fd9e0e55dc9a2e0232b89427d2c4b614
change-id: 20261022-bug-mm-readahead-async-overtaken-b2cf5bedea21
Best regards,
--
Qiliang Yuan <odys.yuan@gmail.com>
reply other threads:[~2026-10-22 15:24 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261022-bug-mm-readahead-async-overtaken-v1-1-c05b8d8bd2da@gmail.com \
--to=odys.yuan@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=jack@suse.cz \
--cc=joannelkoong@gmail.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®