[PATCH 07/11] ntfs: reuse compression output workspace across write units

Namjae Jeon <[email protected]> Tue, 21 Jul 2026 18:54:27 +0900
Newsgroups dev.linux.lists.ntfs
Message-ID <[email protected]>
ntfs_write_cb() allocates output pages and creates input and output
vmaps for every compression unit. Sequential writes repeatedly pay
those allocation and page-table costs even though each unit has the
same maximum output size.
Allocate and map the output workspace once per write request. Access

input sub-blocks with kmap_local_page(), and reuse the output pages and
mapping for every compression unit in the request.

Signed-off-by: Namjae Jeon <[email protected]>
---
 fs/ntfs/compress.c | 135 ++++++++++++++++++++++++++++-----------------
 1 file changed, 85 insertions(+), 50 deletions(-)

diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index f073a53f6136..f3c14518f78e 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -894,6 +894,12 @@ struct compress_context {
 	s16 prev[NTFS_SB_SIZE];
 };
 
+struct ntfs_compress_workspace {
+	struct page **pages;
+	char *outbuf;
+	unsigned int nr_pages;
+};
+
 /*
  * Hash the next 3-byte sequence in the input buffer
  */
@@ -1247,12 +1253,69 @@ static int ntfs_compress_block(struct compress_context *pctx,
 	return xout;
 }
 
+static int ntfs_compress_workspace_init(struct ntfs_inode *ni,
+					struct ntfs_compress_workspace *ws)
+{
+	unsigned int size, i;
+
+	size = ni->itype.compressed.block_size + 2 *
+		(ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2;
+	ws->nr_pages = DIV_ROUND_UP(size, PAGE_SIZE);
+	ws->pages = kcalloc(ws->nr_pages, sizeof(*ws->pages), GFP_NOFS);
+	if (!ws->pages)
+		return -ENOMEM;
+
+	for (i = 0; i < ws->nr_pages; i++) {
+		ws->pages[i] = alloc_page(GFP_NOFS);
+		if (!ws->pages[i])
+			goto free_pages;
+	}
+
+	ws->outbuf = vmap(ws->pages, ws->nr_pages, VM_MAP, PAGE_KERNEL);
+	if (!ws->outbuf)
+		goto free_pages;
+	return 0;
+
+free_pages:
+	while (i)
+		put_page(ws->pages[--i]);
+	kfree(ws->pages);
+	return -ENOMEM;
+}
+
+static void ntfs_compress_workspace_free(struct ntfs_compress_workspace *ws)
+{
+	unsigned int i;
+
+	vunmap(ws->outbuf);
+	for (i = 0; i < ws->nr_pages; i++)
+		put_page(ws->pages[i]);
+	kfree(ws->pages);
+}
+
+static void ntfs_copy_cb(struct page **pages, int pages_per_cb,
+			 unsigned int page_offset,
+			 struct ntfs_compress_workspace *ws, unsigned int bytes)
+{
+	unsigned int copied = 0, i;
+
+	for (i = 0; i < pages_per_cb && copied < bytes; i++) {
+		unsigned int offset = i ? 0 : page_offset;
+		unsigned int len = min(bytes - copied, PAGE_SIZE - offset);
+		void *addr = kmap_local_page(pages[i]);
+
+		memcpy(ws->outbuf + copied, addr + offset, len);
+		kunmap_local(addr);
+		copied += len;
+	}
+}
+
 static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 		int pages_per_cb, unsigned int page_offset,
-		struct compress_context *ctx)
+		struct compress_context *ctx, struct ntfs_compress_workspace *ws)
 {
 	struct ntfs_volume *vol = ni->vol;
-	char *outbuf = NULL, *pbuf, *inbuf, *in_mapping;
+	char *outbuf = ws->outbuf, *pbuf;
 	u32 compsz, p, insz = ni->itype.compressed.block_size;
 	s32 rounded, bio_size;
 	int sz;
@@ -1264,55 +1327,32 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 	static char twozeroes[] = {0x02, 0xb0, 0x00, 0x00, 0x00};
 	/* more compressed zeroes, to be followed by some count */
 	static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00};
-	struct page **pages_disk = NULL, *pg;
 	s64 bio_lcn;
 	struct runlist_element *rlc, *rl;
 	int i, err;
-	int pages_count = (round_up(ni->itype.compressed.block_size + 2 *
-		(ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2, PAGE_SIZE)) / PAGE_SIZE;
 	u32 cb_clusters = ni->itype.compressed.block_clusters;
 	size_t new_rl_count;
 	struct bio *bio = NULL;
 	loff_t cb_pos, new_length;
 	s64 new_vcn;
 
-	in_mapping = vmap(pages, pages_per_cb, VM_MAP, PAGE_KERNEL_RO);
-	if (!in_mapping)
-		return -ENOMEM;
-	inbuf = in_mapping + page_offset;
-
-	/* may need 2 extra bytes per block and 2 more bytes */
-	pages_disk = kcalloc(pages_count, sizeof(struct page *), GFP_NOFS);
-	if (!pages_disk) {
-		vunmap(in_mapping);
-		return -ENOMEM;
-	}
-
-	for (i = 0; i < pages_count; i++) {
-		pg = alloc_page(GFP_KERNEL);
-		if (!pg) {
-			err = -ENOMEM;
-			goto out;
-		}
-		pages_disk[i] = pg;
-		lock_page(pg);
-	}
-
-	outbuf = vmap(pages_disk, pages_count, VM_MAP, PAGE_KERNEL);
-	if (!outbuf) {
-		err = -ENOMEM;
-		goto out;
-	}
-
 	compsz = 0;
 	allzeroes = true;
 	for (p = 0; (p < insz) && !fail; p += NTFS_SB_SIZE) {
+		unsigned int input_offset = page_offset + p;
+		unsigned int page_idx = input_offset >> PAGE_SHIFT;
+		const char *input;
+		void *addr;
+
 		if ((p + NTFS_SB_SIZE) < insz)
 			bsz = NTFS_SB_SIZE;
 		else
 			bsz = insz - p;
 		pbuf = &outbuf[compsz];
-		sz = ntfs_compress_block(ctx, &inbuf[p], bsz, pbuf);
+		addr = kmap_local_page(pages[page_idx]);
+		input = addr + offset_in_page(input_offset);
+		sz = ntfs_compress_block(ctx, input, bsz, pbuf);
+		kunmap_local(addr);
 		if (sz < 0) {
 			err = sz;
 			goto out;
@@ -1352,14 +1392,12 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 		rounded = ((compsz - 1) | (vol->cluster_size - 1)) + 1;
 		memset(&outbuf[compsz], 0, rounded - compsz);
 		bio_size = rounded;
-		pages = pages_disk;
 	} else if (allzeroes) {
 		err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, cb_clusters);
 		goto out;
 	} else {
-		memcpy(outbuf, inbuf, insz);
+		ntfs_copy_cb(pages, pages_per_cb, page_offset, ws, insz);
 		bio_size = insz;
-		pages = pages_disk;
 	}
 
 	new_length = ntfs_bytes_to_cluster(vol, round_up(bio_size, vol->cluster_size));
@@ -1398,7 +1436,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 						((s64)i << PAGE_SHIFT));
 		}
 
-		if (!bio_add_page(bio, pages[i], page_size, 0)) {
+		if (!bio_add_page(bio, ws->pages[i], page_size, 0)) {
 			err = submit_bio_wait(bio);
 			bio_put(bio);
 			if (err)
@@ -1443,17 +1481,6 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 		ntfs_error(vol->sb, "Failed to free hot clusters.");
 	kvfree(rlc);
 out:
-	if (outbuf)
-		vunmap(outbuf);
-	for (i = 0; i < pages_count; i++) {
-		pg = pages_disk[i];
-		if (pg) {
-			unlock_page(pg);
-			put_page(pg);
-		}
-	}
-	kfree(pages_disk);
-	vunmap(in_mapping);
 	NInoSetFileNameDirty(ni);
 	mark_mft_record_dirty(ni);
 
@@ -1463,6 +1490,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
 int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
 		struct iov_iter *from)
 {
+	struct ntfs_compress_workspace ws = {};
 	struct compress_context *ctx;
 	struct folio *folio;
 	struct page **pages = NULL, *page;
@@ -1483,6 +1511,12 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
 		kfree(pages);
 		return -ENOMEM;
 	}
+	err = ntfs_compress_workspace_init(ni, &ws);
+	if (err) {
+		kvfree(ctx);
+		kfree(pages);
+		return err;
+	}
 
 	while (count) {
 		pgoff_t index;
@@ -1551,7 +1585,7 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
 			goto release_pages;
 		}
 
-		err = ntfs_write_cb(ni, pos, pages, pages_per_cb, page_offset, ctx);
+		err = ntfs_write_cb(ni, pos, pages, pages_per_cb, page_offset, ctx, &ws);
 		if (!err && pos + copied > ni->initialized_size) {
 			mutex_lock(&ni->mrec_lock);
 			err = ntfs_attr_set_initialized_size(ni, pos + copied);
@@ -1581,6 +1615,7 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
 	}
 
 out:
+	ntfs_compress_workspace_free(&ws);
 	kvfree(ctx);
 	kfree(pages);
 	if (err < 0)
-- 
2.34.1