[PATCH 1/3] erofs: avoid redundant page copy for aligned shifted/plain pclusters

From: Sarthak Kukreti

Date: Tue Sep 29 2026 - 21:21:32 EST


When a Z_EROFS_COMPRESSION_SHIFTED pcluster is read in-place into its
target page cache folio(s) with page-aligned input and output offsets
(pageofs_in == 0 && pageofs_out == 0), z_erofs_transform_plain() still
maps each page via kmap_local_page() and calls memmove(kin + po,
kin + rq->pageofs_in + pi, cnt) where dst == src. On x86_64,
arch/x86/lib/memmove_64.S does not early-exit when dst == src, executing
rep movsb over the entire 4 KiB page and dirtying all 64 cachelines.

Furthermore, z_erofs_attach_page() restricts uncompressed pclusters to
a single inplace I/O page (fe->icur <= 1), forcing multi-page aligned
Z_EROFS_COMPRESSION_SHIFTED extents to allocate temporary shortlived
pages and copy every page beyond the first via memcpy_to_page().

1. Allow multi-page inplace I/O in z_erofs_attach_page() when the
pcluster uses Z_EROFS_COMPRESSION_SHIFTED with page-aligned input
and output offsets (!pcl->pageofs_in && !pcl->pageofs_out) and full
page bvecs.
2. In z_erofs_transform_plain(), return immediately when a page-aligned
Z_EROFS_COMPRESSION_SHIFTED request has identical input and output
page pointers (rq->in[ni] == rq->out[ni]), and skip memmove() when
po == rq->pageofs_in + pi.

Signed-off-by: Sarthak Kukreti <sarthakkukreti@xxxxxxxxxx>
---
fs/erofs/decompressor.c | 24 ++++++++++++++++++++----
fs/erofs/zdata.c | 14 ++++++++++++--
2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/fs/erofs/decompressor.c b/fs/erofs/decompressor.c
index d387b27c4ee2..0f6517f329ee 100644
--- a/fs/erofs/decompressor.c
+++ b/fs/erofs/decompressor.c
@@ -283,6 +283,21 @@ static const char *z_erofs_transform_plain(struct z_erofs_decompress_req *rq,
rq->outputsize -= cur;
}

+ if (rq->alg == Z_EROFS_COMPRESSION_SHIFTED && !rq->pageofs_in &&
+ !rq->pageofs_out && nrpages_in == nrpages_out) {
+ bool same = true;
+
+ for (ni = 0; ni < nrpages_in; ++ni) {
+ if (!rq->in[ni] || rq->in[ni] != rq->out[ni]) {
+ same = false;
+ break;
+ }
+ }
+ if (same)
+ return NULL;
+ ni = 0;
+ }
+
for (; rq->outputsize; rq->pageofs_in = 0, cur += insz, ni++) {
insz = min(PAGE_SIZE - rq->pageofs_in, rq->outputsize);
rq->outputsize -= insz;
@@ -295,10 +310,11 @@ static const char *z_erofs_transform_plain(struct z_erofs_decompress_req *rq,
po = (rq->pageofs_out + cur + pi) & ~PAGE_MASK;
DBG_BUGON(no >= nrpages_out);
cnt = min(insz - pi, PAGE_SIZE - po);
- if (rq->out[no] == rq->in[ni])
- memmove(kin + po,
- kin + rq->pageofs_in + pi, cnt);
- else if (rq->out[no])
+ if (rq->out[no] == rq->in[ni]) {
+ if (po != rq->pageofs_in + pi)
+ memmove(kin + po,
+ kin + rq->pageofs_in + pi, cnt);
+ } else if (rq->out[no])
memcpy_to_page(rq->out[no], po,
kin + rq->pageofs_in + pi, cnt);
pi += cnt;
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index 6b07e73ee2aa..0517caf61959 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -698,9 +698,19 @@ static int z_erofs_attach_page(struct z_erofs_frontend *fe,
int ret;

if (exclusive) {
- /* Inplace I/O is limited to one page for uncompressed data */
+ /*
+ * Allow multi-page inplace I/O for page-aligned SHIFTED
+ * pclusters so that uncompressed multi-block extents read
+ * directly into the target page cache folios without allocating
+ * intermediate shortlived pages or copying in
+ * z_erofs_transform_plain().
+ */
if (pcl->algorithmformat < Z_EROFS_COMPRESSION_MAX ||
- fe->icur <= 1) {
+ fe->icur <= 1 ||
+ (pcl->algorithmformat == Z_EROFS_COMPRESSION_SHIFTED &&
+ !pcl->pageofs_in && !pcl->pageofs_out &&
+ !(bvec->offset & ~PAGE_MASK) &&
+ bvec->end == PAGE_SIZE)) {
/* Try to prioritize inplace I/O here */
spin_lock(&pcl->lockref.lock);
while (fe->icur > 0) {
--
2.56.0.rc1.315.gc6ed9934b7-goog