public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v1] f2fs: map data writes to FDP streams
@ 2026-04-15  8:14 Wenjie Qi
  2026-04-17  2:17 ` Chao Yu
  0 siblings, 1 reply; 2+ messages in thread
From: Wenjie Qi @ 2026-04-15  8:14 UTC (permalink / raw)
  To: jaegeuk, chao; +Cc: linux-f2fs-devel, linux-kernel, qwjhust

From: Wenjie Qi <qwjhust@gmail.com>

From: Wenjie Qi <qiwenjie@xiaomi.com>

F2FS already classifies DATA writes using its existing hot, warm and cold
temperature policy, but it only passes that intent down as a write hint.
That hint alone is not sufficient for NVMe FDP placement, because the
current NVMe command path consumes `bio->bi_write_stream` rather than
`bio->bi_write_hint` when selecting a placement ID.

When the target block device exposes write streams, map the existing F2FS
DATA temperature classes onto stream IDs and set `bio->bi_write_stream`
for both buffered and direct writes. If the device exposes no write
streams, keep the current behavior by leaving the stream unset.

The stream mapping is evaluated against the target block device of each
bio, so the existing per-device fallback behavior stays unchanged for
multi-device filesystems. Existing blkzoned restrictions also remain in
place.

The mapping is intentionally small and deterministic:

- 1 stream: hot, warm and cold all use stream 1
- 2 streams: hot/warm use 1, cold uses 2
- 3+ streams: hot uses 1, warm uses 2, cold uses 3

Signed-off-by: Wenjie Qi <qwjhust@gmail.com>

---
RFC notes:
- Scope is intentionally limited to DATA writes only.
- Validation used stock QEMU 8.2.2 NVMe FDP emulation with a 16G
  namespace.
- FDP enabled: buffered and direct writes each produced 3 dominant
  NVMe placement tuples.
- FDP disabled: buffered and direct writes each collapsed to 1
  dominant tuple.

 fs/f2fs/data.c    |  2 ++
 fs/f2fs/f2fs.h    |  2 ++
 fs/f2fs/file.c    |  2 ++
 fs/f2fs/segment.c | 24 ++++++++++++++++++++++++
 4 files changed, 30 insertions(+)

diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 338df7a2a..df9f4a230 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -508,6 +508,8 @@ static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages)
 		bio->bi_private = sbi;
 		bio->bi_write_hint = f2fs_io_type_to_rw_hint(sbi,
 						fio->type, fio->temp);
+		bio->bi_write_stream = f2fs_io_type_to_write_stream(bdev, fio->type,
+								    fio->temp);
 	}
 	iostat_alloc_and_bind_ctx(sbi, bio, NULL);
 
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index bb34e864d..2f0777478 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -4056,6 +4056,8 @@ void f2fs_destroy_segment_manager_caches(void);
 int f2fs_rw_hint_to_seg_type(struct f2fs_sb_info *sbi, enum rw_hint hint);
 enum rw_hint f2fs_io_type_to_rw_hint(struct f2fs_sb_info *sbi,
 			enum page_type type, enum temp_type temp);
+u8 f2fs_io_type_to_write_stream(struct block_device *bdev,
+				enum page_type type, enum temp_type temp);
 unsigned int f2fs_usable_segs_in_sec(struct f2fs_sb_info *sbi);
 unsigned int f2fs_usable_blks_in_seg(struct f2fs_sb_info *sbi,
 			unsigned int segno);
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index c8a2f17a8..a3186cfc9 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -5064,6 +5064,8 @@ static void f2fs_dio_write_submit_io(const struct iomap_iter *iter,
 	enum temp_type temp = f2fs_get_segment_temp(sbi, type);
 
 	bio->bi_write_hint = f2fs_io_type_to_rw_hint(sbi, DATA, temp);
+	bio->bi_write_stream =
+		f2fs_io_type_to_write_stream(bio->bi_bdev, DATA, temp);
 	blk_crypto_submit_bio(bio);
 }
 
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 6a97fe767..ef8a3ac55 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -3626,6 +3626,30 @@ enum rw_hint f2fs_io_type_to_rw_hint(struct f2fs_sb_info *sbi,
 	}
 }
 
+u8 f2fs_io_type_to_write_stream(struct block_device *bdev,
+				enum page_type type, enum temp_type temp)
+{
+	unsigned short nr = bdev_max_write_streams(bdev);
+
+	if (type != DATA || !nr)
+		return 0;
+	if (nr == 1)
+		return 1;
+	if (nr == 2)
+		return temp == COLD ? 2 : 1;
+
+	switch (temp) {
+	case HOT:
+		return 1;
+	case WARM:
+		return 2;
+	case COLD:
+		return 3;
+	default:
+		return 0;
+	}
+}
+
 static int __get_segment_type_2(struct f2fs_io_info *fio)
 {
 	if (fio->type == DATA)
-- 
2.43.0


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

* Re: [RFC PATCH v1] f2fs: map data writes to FDP streams
  2026-04-15  8:14 [RFC PATCH v1] f2fs: map data writes to FDP streams Wenjie Qi
@ 2026-04-17  2:17 ` Chao Yu
  0 siblings, 0 replies; 2+ messages in thread
From: Chao Yu @ 2026-04-17  2:17 UTC (permalink / raw)
  To: Wenjie Qi, jaegeuk; +Cc: chao, linux-f2fs-devel, linux-kernel

On 4/15/2026 4:14 PM, Wenjie Qi wrote:
> From: Wenjie Qi <qwjhust@gmail.com>
> 
> From: Wenjie Qi <qiwenjie@xiaomi.com>
> 
> F2FS already classifies DATA writes using its existing hot, warm and cold
> temperature policy, but it only passes that intent down as a write hint.
> That hint alone is not sufficient for NVMe FDP placement, because the
> current NVMe command path consumes `bio->bi_write_stream` rather than
> `bio->bi_write_hint` when selecting a placement ID.
> 
> When the target block device exposes write streams, map the existing F2FS
> DATA temperature classes onto stream IDs and set `bio->bi_write_stream`
> for both buffered and direct writes. If the device exposes no write
> streams, keep the current behavior by leaving the stream unset.
> 
> The stream mapping is evaluated against the target block device of each
> bio, so the existing per-device fallback behavior stays unchanged for
> multi-device filesystems. Existing blkzoned restrictions also remain in
> place.
> 
> The mapping is intentionally small and deterministic:
> 
> - 1 stream: hot, warm and cold all use stream 1
> - 2 streams: hot/warm use 1, cold uses 2
> - 3+ streams: hot uses 1, warm uses 2, cold uses 3

Hi Wenjie,

Can we add doc to describe the default config?

> 
> Signed-off-by: Wenjie Qi <qwjhust@gmail.com>
> 
> ---
> RFC notes:
> - Scope is intentionally limited to DATA writes only.
> - Validation used stock QEMU 8.2.2 NVMe FDP emulation with a 16G
>    namespace.
> - FDP enabled: buffered and direct writes each produced 3 dominant
>    NVMe placement tuples.
> - FDP disabled: buffered and direct writes each collapsed to 1
>    dominant tuple.
> 
>   fs/f2fs/data.c    |  2 ++
>   fs/f2fs/f2fs.h    |  2 ++
>   fs/f2fs/file.c    |  2 ++
>   fs/f2fs/segment.c | 24 ++++++++++++++++++++++++
>   4 files changed, 30 insertions(+)
> 
> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
> index 338df7a2a..df9f4a230 100644
> --- a/fs/f2fs/data.c
> +++ b/fs/f2fs/data.c
> @@ -508,6 +508,8 @@ static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages)
>   		bio->bi_private = sbi;
>   		bio->bi_write_hint = f2fs_io_type_to_rw_hint(sbi,
>   						fio->type, fio->temp);
> +		bio->bi_write_stream = f2fs_io_type_to_write_stream(bdev, fio->type,
> +								    fio->temp);
>   	}
>   	iostat_alloc_and_bind_ctx(sbi, bio, NULL);
>   
> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
> index bb34e864d..2f0777478 100644
> --- a/fs/f2fs/f2fs.h
> +++ b/fs/f2fs/f2fs.h
> @@ -4056,6 +4056,8 @@ void f2fs_destroy_segment_manager_caches(void);
>   int f2fs_rw_hint_to_seg_type(struct f2fs_sb_info *sbi, enum rw_hint hint);
>   enum rw_hint f2fs_io_type_to_rw_hint(struct f2fs_sb_info *sbi,
>   			enum page_type type, enum temp_type temp);
> +u8 f2fs_io_type_to_write_stream(struct block_device *bdev,
> +				enum page_type type, enum temp_type temp);
>   unsigned int f2fs_usable_segs_in_sec(struct f2fs_sb_info *sbi);
>   unsigned int f2fs_usable_blks_in_seg(struct f2fs_sb_info *sbi,
>   			unsigned int segno);
> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> index c8a2f17a8..a3186cfc9 100644
> --- a/fs/f2fs/file.c
> +++ b/fs/f2fs/file.c
> @@ -5064,6 +5064,8 @@ static void f2fs_dio_write_submit_io(const struct iomap_iter *iter,
>   	enum temp_type temp = f2fs_get_segment_temp(sbi, type);
>   
>   	bio->bi_write_hint = f2fs_io_type_to_rw_hint(sbi, DATA, temp);
> +	bio->bi_write_stream =
> +		f2fs_io_type_to_write_stream(bio->bi_bdev, DATA, temp);
>   	blk_crypto_submit_bio(bio);
>   }
>   
> diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
> index 6a97fe767..ef8a3ac55 100644
> --- a/fs/f2fs/segment.c
> +++ b/fs/f2fs/segment.c
> @@ -3626,6 +3626,30 @@ enum rw_hint f2fs_io_type_to_rw_hint(struct f2fs_sb_info *sbi,
>   	}
>   }
>   
> +u8 f2fs_io_type_to_write_stream(struct block_device *bdev,
> +				enum page_type type, enum temp_type temp)
> +{
> +	unsigned short nr = bdev_max_write_streams(bdev);
> +
> +	if (type != DATA || !nr)
> +		return 0;
> +	if (nr == 1)
> +		return 1;
> +	if (nr == 2)
> +		return temp == COLD ? 2 : 1;
> +
> +	switch (temp) {
> +	case HOT:
> +		return 1;
> +	case WARM:
> +		return 2;
> +	case COLD:
> +		return 3;
> +	default:
> +		return 0;

How about using existing macros if there is instead magic numbers?

Thanks,

> +	}
> +}
> +
>   static int __get_segment_type_2(struct f2fs_io_info *fio)
>   {
>   	if (fio->type == DATA)


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

end of thread, other threads:[~2026-04-17  2:17 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-15  8:14 [RFC PATCH v1] f2fs: map data writes to FDP streams Wenjie Qi
2026-04-17  2:17 ` Chao Yu

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