public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* (no subject)
@ 2026-02-04  7:14 Chi Zhiling
  2026-02-04  7:14 ` [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster Chi Zhiling
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-02-04  7:14 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

Subject: [PATCH v1 0/3] exfat: optimize exfat_chain_cont_cluster for large file conversion

When an exFAT file cannot allocate contiguous space, it converts from
NO_FAT_CHAIN to FAT_CHAIN format. For large files, this conversion can
be extremely slow due to:
1. Sequential FAT block reads without readahead
2. Mark buffer dirty and mirroring operations for each cluster

This series addresses these bottlenecks through two optimizations:

1. Block readahead: Read-ahead consecutive FAT blocks to reduce I/O wait
2. Buffer caching: Cache buffer heads and commit dirty buffer per block
   instead of per cluster

Performance improvements for converting a 30GB file:
| Cluster Size | Original | After Patches | Speedup |
|--------------|----------|---------------|---------|
| 512 bytes    | 47.667s  | 1.866s        | 25.5x   |
| 4KB          | 6.436s   | 0.236s        | 27.3x   |
| 32KB         | 0.758s   | 0.034s        | 22.3x   |
| 256KB        | 0.117s   | 0.006s        | 19.5x   |

All criticism and suggestions are welcome :)


Chi Zhiling (3):
  exfat: add block readahead in exfat_chain_cont_cluster
  exfat: drop parameter sec for exfat_mirror_bh
  exfat: optimize exfat_chain_cont_cluster with cached buffer heads

 fs/exfat/exfat_fs.h |  9 ++++-
 fs/exfat/fatent.c   | 96 ++++++++++++++++++++++++++++++++++++---------
 2 files changed, 85 insertions(+), 20 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster
  2026-02-04  7:14 Chi Zhiling
@ 2026-02-04  7:14 ` Chi Zhiling
  2026-02-05  1:39   ` Namjae Jeon
  2026-02-04  7:14 ` [PATCH v1 2/3] exfat: drop parameter sec for exfat_mirror_bh Chi Zhiling
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 7+ messages in thread
From: Chi Zhiling @ 2026-02-04  7:14 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

The conversion from NO_FAT_CHAIN format to FAT_CHAIN format occurs
when the file cannot allocate contiguous space. When the file to be
converted is very large, this process can take a long time.

This patch introduces simple readahead to read all the blocks in
advance, as these blocks are consecutive.

Test in an empty exfat filesystem:
dd if=/dev/zero of=/mnt/file bs=1M count=30k
dd if=/dev/zero of=/mnt/file2 bs=1M count=1
time cat /mnt/file2 >> /mnt/file

| cluster size | before patch | after patch |
| ------------ | ------------ | ----------- |
| 512          | 47.667s      | 4.316s      |
| 4k           | 6.436s       | 0.541s      |
| 32k          | 0.758s       | 0.071s      |
| 256k         | 0.117s       | 0.011s      |

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/exfat_fs.h |  9 +++++++--
 fs/exfat/fatent.c   | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 2dbed5f8ec26..5a3cdf725846 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -10,6 +10,7 @@
 #include <linux/ratelimit.h>
 #include <linux/nls.h>
 #include <linux/blkdev.h>
+#include <linux/backing-dev.h>
 #include <uapi/linux/exfat.h>
 
 #define EXFAT_ROOT_INO		1
@@ -79,6 +80,10 @@ enum {
 #define EXFAT_HINT_NONE		-1
 #define EXFAT_MIN_SUBDIR	2
 
+#define EXFAT_BLK_RA_SIZE(sb)		\
+(min((sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
+	 << (PAGE_SHIFT - sb->s_blocksize_bits))
+
 /*
  * helpers for cluster size to byte conversion.
  */
@@ -117,9 +122,9 @@ enum {
 #define FAT_ENT_SIZE (4)
 #define FAT_ENT_SIZE_BITS (2)
 #define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \
-	(((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
+	(((u64)(loc) << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
 #define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc)	\
-	((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
+	(((loc) << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
 
 /*
  * helpers for bitmap.
diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index 71ee16479c43..0c17621587d5 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -142,13 +142,51 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc,
 	return -EIO;
 }
 
+static int exfat_blk_readahead(struct super_block *sb, sector_t sec,
+		sector_t *ra, blkcnt_t *ra_cnt, sector_t end)
+{
+	struct blk_plug plug;
+
+	if (sec < *ra)
+		return 0;
+
+	*ra += *ra_cnt;
+
+	/* No blocks left (or only the last block), skip readahead. */
+	if (*ra >= end)
+		return 0;
+
+	*ra_cnt = min(end - *ra + 1, EXFAT_BLK_RA_SIZE(sb));
+	if (*ra_cnt == 0) {
+		/* Move 'ra' to the end to disable readahead. */
+		*ra = end;
+		return 0;
+	}
+
+	blk_start_plug(&plug);
+	for (unsigned int i = 0; i < *ra_cnt; i++)
+		sb_breadahead(sb, *ra + i);
+	blk_finish_plug(&plug);
+	return 0;
+}
+
 int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
 		unsigned int len)
 {
+	sector_t sec, end, ra;
+	blkcnt_t ra_cnt;
+
 	if (!len)
 		return 0;
 
+	ra_cnt = 0;
+	ra = FAT_ENT_OFFSET_SECTOR(sb, chain);
+	end = FAT_ENT_OFFSET_SECTOR(sb, chain + len - 1);
+
 	while (len > 1) {
+		sec = FAT_ENT_OFFSET_SECTOR(sb, chain);
+		exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end);
+
 		if (exfat_ent_set(sb, chain, chain + 1))
 			return -EIO;
 		chain++;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v1 2/3] exfat: drop parameter sec for exfat_mirror_bh
  2026-02-04  7:14 Chi Zhiling
  2026-02-04  7:14 ` [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster Chi Zhiling
@ 2026-02-04  7:14 ` Chi Zhiling
  2026-02-04  7:14 ` [PATCH v1 3/3] exfat: optimize exfat_chain_cont_cluster with cached buffer heads Chi Zhiling
  2026-02-04  7:19 ` Chi Zhiling
  3 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-02-04  7:14 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

The sector offset can be obtained from bh->b_blocknr, so drop the
parameter and do some cleanups.

No functional changes.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/fatent.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index 0c17621587d5..0bc54aa5d122 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -11,11 +11,11 @@
 #include "exfat_raw.h"
 #include "exfat_fs.h"
 
-static int exfat_mirror_bh(struct super_block *sb, sector_t sec,
-		struct buffer_head *bh)
+static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh)
 {
 	struct buffer_head *c_bh;
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	sector_t sec = bh->b_blocknr;
 	sector_t sec2;
 	int err = 0;
 
@@ -25,10 +25,7 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec,
 		if (!c_bh)
 			return -ENOMEM;
 		memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize);
-		set_buffer_uptodate(c_bh);
-		mark_buffer_dirty(c_bh);
-		if (sb->s_flags & SB_SYNCHRONOUS)
-			err = sync_dirty_buffer(c_bh);
+		exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS);
 		brelse(c_bh);
 	}
 
@@ -83,7 +80,7 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc,
 	fat_entry = (__le32 *)&(bh->b_data[off]);
 	*fat_entry = cpu_to_le32(content);
 	exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
-	exfat_mirror_bh(sb, sec, bh);
+	exfat_mirror_bh(sb, bh);
 	brelse(bh);
 	return 0;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v1 3/3] exfat: optimize exfat_chain_cont_cluster with cached buffer heads
  2026-02-04  7:14 Chi Zhiling
  2026-02-04  7:14 ` [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster Chi Zhiling
  2026-02-04  7:14 ` [PATCH v1 2/3] exfat: drop parameter sec for exfat_mirror_bh Chi Zhiling
@ 2026-02-04  7:14 ` Chi Zhiling
  2026-02-04  7:19 ` Chi Zhiling
  3 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-02-04  7:14 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

When converting files from NO_FAT_CHAIN to FAT_CHAIN format, significant time
spent in mark_buffer_dirty() and exfat_mirror_bh() operations.
This overhead occurs because each FAT entry modification triggers a full block
dirty marking and mirroring operation.

For consecutive clusters that reside in the same block, we can optimize
by caching the buffer head and performing dirty marking only once at
the end of the block's modifications.

Performance improvements for converting a 30GB file:

| Cluster Size | Before Patch | After Patch | Speedup |
|--------------|--------------|-------------|---------|
| 512 bytes    | 4.316s       | 1.866s      | 2.31x   |
| 4KB          | 0.541s       | 0.236s      | 2.29x   |
| 32KB         | 0.071s       | 0.034s      | 2.09x   |
| 256KB        | 0.011s       | 0.006s      | 1.83x   |

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/fatent.c | 49 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 37 insertions(+), 12 deletions(-)

diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index 0bc54aa5d122..30d88071e97f 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -32,6 +32,17 @@ static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh)
 	return err;
 }
 
+static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh)
+{
+	int err;
+
+	exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
+	err = exfat_mirror_bh(sb, bh);
+	brelse(bh);
+
+	return err;
+}
+
 static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
 		unsigned int *content, struct buffer_head **last)
 {
@@ -62,29 +73,40 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
 	return 0;
 }
 
-int exfat_ent_set(struct super_block *sb, unsigned int loc,
-		unsigned int content)
+static int __exfat_ent_set(struct super_block *sb, unsigned int loc,
+		unsigned int content, struct buffer_head **cache)
 {
-	unsigned int off;
 	sector_t sec;
 	__le32 *fat_entry;
-	struct buffer_head *bh;
+	struct buffer_head *bh = cache ? *cache : NULL;
+	unsigned int off;
 
 	sec = FAT_ENT_OFFSET_SECTOR(sb, loc);
 	off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc);
 
-	bh = sb_bread(sb, sec);
-	if (!bh)
-		return -EIO;
+	if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
+		if (bh)
+			exfat_end_bh(sb, bh);
+		bh = sb_bread(sb, sec);
+		if (cache)
+			*cache = bh;
+		if (unlikely(!bh))
+			return -EIO;
+	}
 
 	fat_entry = (__le32 *)&(bh->b_data[off]);
 	*fat_entry = cpu_to_le32(content);
-	exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
-	exfat_mirror_bh(sb, bh);
-	brelse(bh);
+	if (!cache)
+		exfat_end_bh(sb, bh);
 	return 0;
 }
 
+int exfat_ent_set(struct super_block *sb, unsigned int loc,
+		unsigned int content)
+{
+	return __exfat_ent_set(sb, loc, content, NULL);
+}
+
 /*
  * Caller must release the buffer_head if no error return.
  */
@@ -170,6 +192,7 @@ static int exfat_blk_readahead(struct super_block *sb, sector_t sec,
 int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
 		unsigned int len)
 {
+	struct buffer_head *bh = NULL;
 	sector_t sec, end, ra;
 	blkcnt_t ra_cnt;
 
@@ -184,14 +207,16 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
 		sec = FAT_ENT_OFFSET_SECTOR(sb, chain);
 		exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end);
 
-		if (exfat_ent_set(sb, chain, chain + 1))
+		if (__exfat_ent_set(sb, chain, chain + 1, &bh))
 			return -EIO;
 		chain++;
 		len--;
 	}
 
-	if (exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER))
+	if (__exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER, &bh))
 		return -EIO;
+
+	exfat_end_bh(sb, bh);
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v1 3/3] exfat: optimize exfat_chain_cont_cluster with cached buffer heads
  2026-02-04  7:14 Chi Zhiling
                   ` (2 preceding siblings ...)
  2026-02-04  7:14 ` [PATCH v1 3/3] exfat: optimize exfat_chain_cont_cluster with cached buffer heads Chi Zhiling
@ 2026-02-04  7:19 ` Chi Zhiling
  3 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-02-04  7:19 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

When converting files from NO_FAT_CHAIN to FAT_CHAIN format, significant time
spent in mark_buffer_dirty() and exfat_mirror_bh() operations.
This overhead occurs because each FAT entry modification triggers a full block
dirty marking and mirroring operation.

For consecutive clusters that reside in the same block, we can optimize
by caching the buffer head and performing dirty marking only once at
the end of the block's modifications.

Performance improvements for converting a 30GB file:

| Cluster Size | Before Patch | After Patch | Speedup |
|--------------|--------------|-------------|---------|
| 512 bytes    | 4.316s       | 1.866s      | 2.31x   |
| 4KB          | 0.541s       | 0.236s      | 2.29x   |
| 32KB         | 0.071s       | 0.034s      | 2.09x   |
| 256KB        | 0.011s       | 0.006s      | 1.83x   |

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/fatent.c | 49 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 37 insertions(+), 12 deletions(-)

diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index 0bc54aa5d122..30d88071e97f 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -32,6 +32,17 @@ static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh)
 	return err;
 }
 
+static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh)
+{
+	int err;
+
+	exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
+	err = exfat_mirror_bh(sb, bh);
+	brelse(bh);
+
+	return err;
+}
+
 static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
 		unsigned int *content, struct buffer_head **last)
 {
@@ -62,29 +73,40 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
 	return 0;
 }
 
-int exfat_ent_set(struct super_block *sb, unsigned int loc,
-		unsigned int content)
+static int __exfat_ent_set(struct super_block *sb, unsigned int loc,
+		unsigned int content, struct buffer_head **cache)
 {
-	unsigned int off;
 	sector_t sec;
 	__le32 *fat_entry;
-	struct buffer_head *bh;
+	struct buffer_head *bh = cache ? *cache : NULL;
+	unsigned int off;
 
 	sec = FAT_ENT_OFFSET_SECTOR(sb, loc);
 	off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc);
 
-	bh = sb_bread(sb, sec);
-	if (!bh)
-		return -EIO;
+	if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
+		if (bh)
+			exfat_end_bh(sb, bh);
+		bh = sb_bread(sb, sec);
+		if (cache)
+			*cache = bh;
+		if (unlikely(!bh))
+			return -EIO;
+	}
 
 	fat_entry = (__le32 *)&(bh->b_data[off]);
 	*fat_entry = cpu_to_le32(content);
-	exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
-	exfat_mirror_bh(sb, bh);
-	brelse(bh);
+	if (!cache)
+		exfat_end_bh(sb, bh);
 	return 0;
 }
 
+int exfat_ent_set(struct super_block *sb, unsigned int loc,
+		unsigned int content)
+{
+	return __exfat_ent_set(sb, loc, content, NULL);
+}
+
 /*
  * Caller must release the buffer_head if no error return.
  */
@@ -170,6 +192,7 @@ static int exfat_blk_readahead(struct super_block *sb, sector_t sec,
 int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
 		unsigned int len)
 {
+	struct buffer_head *bh = NULL;
 	sector_t sec, end, ra;
 	blkcnt_t ra_cnt;
 
@@ -184,14 +207,16 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
 		sec = FAT_ENT_OFFSET_SECTOR(sb, chain);
 		exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end);
 
-		if (exfat_ent_set(sb, chain, chain + 1))
+		if (__exfat_ent_set(sb, chain, chain + 1, &bh))
 			return -EIO;
 		chain++;
 		len--;
 	}
 
-	if (exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER))
+	if (__exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER, &bh))
 		return -EIO;
+
+	exfat_end_bh(sb, bh);
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster
  2026-02-04  7:14 ` [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster Chi Zhiling
@ 2026-02-05  1:39   ` Namjae Jeon
  2026-02-05  2:13     ` Chi Zhiling
  0 siblings, 1 reply; 7+ messages in thread
From: Namjae Jeon @ 2026-02-05  1:39 UTC (permalink / raw)
  To: Chi Zhiling
  Cc: linux-fsdevel, linux-kernel, Sungjong Seo, Yuezhang Mo,
	Chi Zhiling

On Wed, Feb 4, 2026 at 4:15 PM Chi Zhiling <chizhiling@163.com> wrote:
>
> From: Chi Zhiling <chizhiling@kylinos.cn>
>
> The conversion from NO_FAT_CHAIN format to FAT_CHAIN format occurs
> when the file cannot allocate contiguous space. When the file to be
> converted is very large, this process can take a long time.
>
> This patch introduces simple readahead to read all the blocks in
> advance, as these blocks are consecutive.
>
> Test in an empty exfat filesystem:
> dd if=/dev/zero of=/mnt/file bs=1M count=30k
> dd if=/dev/zero of=/mnt/file2 bs=1M count=1
> time cat /mnt/file2 >> /mnt/file
>
> | cluster size | before patch | after patch |
> | ------------ | ------------ | ----------- |
> | 512          | 47.667s      | 4.316s      |
> | 4k           | 6.436s       | 0.541s      |
> | 32k          | 0.758s       | 0.071s      |
> | 256k         | 0.117s       | 0.011s      |
>
> Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
> ---
>  fs/exfat/exfat_fs.h |  9 +++++++--
>  fs/exfat/fatent.c   | 38 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 45 insertions(+), 2 deletions(-)
>
> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
> index 2dbed5f8ec26..5a3cdf725846 100644
> --- a/fs/exfat/exfat_fs.h
> +++ b/fs/exfat/exfat_fs.h
> @@ -10,6 +10,7 @@
>  #include <linux/ratelimit.h>
>  #include <linux/nls.h>
>  #include <linux/blkdev.h>
> +#include <linux/backing-dev.h>
>  #include <uapi/linux/exfat.h>
>
>  #define EXFAT_ROOT_INO         1
> @@ -79,6 +80,10 @@ enum {
>  #define EXFAT_HINT_NONE                -1
>  #define EXFAT_MIN_SUBDIR       2
>
> +#define EXFAT_BLK_RA_SIZE(sb)          \
> +(min((sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
> +        << (PAGE_SHIFT - sb->s_blocksize_bits))
> +
>  /*
>   * helpers for cluster size to byte conversion.
>   */
> @@ -117,9 +122,9 @@ enum {
>  #define FAT_ENT_SIZE (4)
>  #define FAT_ENT_SIZE_BITS (2)
>  #define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \
> -       (((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
> +       (((u64)(loc) << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
>  #define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc) \
> -       ((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
> +       (((loc) << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
>
>  /*
>   * helpers for bitmap.
> diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
> index 71ee16479c43..0c17621587d5 100644
> --- a/fs/exfat/fatent.c
> +++ b/fs/exfat/fatent.c
> @@ -142,13 +142,51 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc,
>         return -EIO;
>  }
>
> +static int exfat_blk_readahead(struct super_block *sb, sector_t sec,
> +               sector_t *ra, blkcnt_t *ra_cnt, sector_t end)
> +{
> +       struct blk_plug plug;
> +
> +       if (sec < *ra)
> +               return 0;
> +
> +       *ra += *ra_cnt;
> +
> +       /* No blocks left (or only the last block), skip readahead. */
> +       if (*ra >= end)
> +               return 0;
> +
> +       *ra_cnt = min(end - *ra + 1, EXFAT_BLK_RA_SIZE(sb));
> +       if (*ra_cnt == 0) {
> +               /* Move 'ra' to the end to disable readahead. */
> +               *ra = end;
> +               return 0;
> +       }
> +
> +       blk_start_plug(&plug);
> +       for (unsigned int i = 0; i < *ra_cnt; i++)
> +               sb_breadahead(sb, *ra + i);
> +       blk_finish_plug(&plug);
> +       return 0;
> +}
Can you combine multiple readahead codes (readahead in
exfat_allocate_bitmap, exfat_dir_readahead) in exfat?
Thanks.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster
  2026-02-05  1:39   ` Namjae Jeon
@ 2026-02-05  2:13     ` Chi Zhiling
  0 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-02-05  2:13 UTC (permalink / raw)
  To: Namjae Jeon
  Cc: linux-fsdevel, linux-kernel, Sungjong Seo, Yuezhang Mo,
	Chi Zhiling

On 2/5/26 09:39, Namjae Jeon wrote:
> On Wed, Feb 4, 2026 at 4:15 PM Chi Zhiling <chizhiling@163.com> wrote:
>>
>> From: Chi Zhiling <chizhiling@kylinos.cn>
>>
>> The conversion from NO_FAT_CHAIN format to FAT_CHAIN format occurs
>> when the file cannot allocate contiguous space. When the file to be
>> converted is very large, this process can take a long time.
>>
>> This patch introduces simple readahead to read all the blocks in
>> advance, as these blocks are consecutive.
>>
>> Test in an empty exfat filesystem:
>> dd if=/dev/zero of=/mnt/file bs=1M count=30k
>> dd if=/dev/zero of=/mnt/file2 bs=1M count=1
>> time cat /mnt/file2 >> /mnt/file
>>
>> | cluster size | before patch | after patch |
>> | ------------ | ------------ | ----------- |
>> | 512          | 47.667s      | 4.316s      |
>> | 4k           | 6.436s       | 0.541s      |
>> | 32k          | 0.758s       | 0.071s      |
>> | 256k         | 0.117s       | 0.011s      |
>>
>> Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
>> ---
>>   fs/exfat/exfat_fs.h |  9 +++++++--
>>   fs/exfat/fatent.c   | 38 ++++++++++++++++++++++++++++++++++++++
>>   2 files changed, 45 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
>> index 2dbed5f8ec26..5a3cdf725846 100644
>> --- a/fs/exfat/exfat_fs.h
>> +++ b/fs/exfat/exfat_fs.h
>> @@ -10,6 +10,7 @@
>>   #include <linux/ratelimit.h>
>>   #include <linux/nls.h>
>>   #include <linux/blkdev.h>
>> +#include <linux/backing-dev.h>
>>   #include <uapi/linux/exfat.h>
>>
>>   #define EXFAT_ROOT_INO         1
>> @@ -79,6 +80,10 @@ enum {
>>   #define EXFAT_HINT_NONE                -1
>>   #define EXFAT_MIN_SUBDIR       2
>>
>> +#define EXFAT_BLK_RA_SIZE(sb)          \
>> +(min((sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
>> +        << (PAGE_SHIFT - sb->s_blocksize_bits))
>> +
>>   /*
>>    * helpers for cluster size to byte conversion.
>>    */
>> @@ -117,9 +122,9 @@ enum {
>>   #define FAT_ENT_SIZE (4)
>>   #define FAT_ENT_SIZE_BITS (2)
>>   #define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \
>> -       (((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
>> +       (((u64)(loc) << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits))
>>   #define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc) \
>> -       ((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
>> +       (((loc) << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1))
>>
>>   /*
>>    * helpers for bitmap.
>> diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
>> index 71ee16479c43..0c17621587d5 100644
>> --- a/fs/exfat/fatent.c
>> +++ b/fs/exfat/fatent.c
>> @@ -142,13 +142,51 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc,
>>          return -EIO;
>>   }
>>
>> +static int exfat_blk_readahead(struct super_block *sb, sector_t sec,
>> +               sector_t *ra, blkcnt_t *ra_cnt, sector_t end)
>> +{
>> +       struct blk_plug plug;
>> +
>> +       if (sec < *ra)
>> +               return 0;
>> +
>> +       *ra += *ra_cnt;
>> +
>> +       /* No blocks left (or only the last block), skip readahead. */
>> +       if (*ra >= end)
>> +               return 0;
>> +
>> +       *ra_cnt = min(end - *ra + 1, EXFAT_BLK_RA_SIZE(sb));
>> +       if (*ra_cnt == 0) {
>> +               /* Move 'ra' to the end to disable readahead. */
>> +               *ra = end;
>> +               return 0;
>> +       }
>> +
>> +       blk_start_plug(&plug);
>> +       for (unsigned int i = 0; i < *ra_cnt; i++)
>> +               sb_breadahead(sb, *ra + i);
>> +       blk_finish_plug(&plug);
>> +       return 0;
>> +}
> Can you combine multiple readahead codes (readahead in
> exfat_allocate_bitmap, exfat_dir_readahead) in exfat?

OK, I will do this in v2.

> Thanks.


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-02-05  2:13 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-04  7:14 Chi Zhiling
2026-02-04  7:14 ` [PATCH v1 1/3] exfat: add block readahead in exfat_chain_cont_cluster Chi Zhiling
2026-02-05  1:39   ` Namjae Jeon
2026-02-05  2:13     ` Chi Zhiling
2026-02-04  7:14 ` [PATCH v1 2/3] exfat: drop parameter sec for exfat_mirror_bh Chi Zhiling
2026-02-04  7:14 ` [PATCH v1 3/3] exfat: optimize exfat_chain_cont_cluster with cached buffer heads Chi Zhiling
2026-02-04  7:19 ` Chi Zhiling

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox