On 6/15/26 8:05 PM, Chao Yu via Linux-f2fs-devel wrote:
On 6/12/26 19:58, Yongpeng Yang wrote:
From: Yongpeng Yang <[email protected]>

Introduce enum extent_access_mode to classify how each extent node
is accessed or created (READ, WRITE, PRECACHE, TRUNCATE, LARGEST).
This metadata optimize LRU eviction decisions:

Can you please give some numbers for this change?

This is a qualitative analysis. This patch aims to prioritize shrinking
extents that are not read-hit and the largest extent.




1. Extents only accessed as the largest extent (never read-hit) are
deprioritized in the LRU list since reads can still use the largest
extent directly.

2. Sparse single-block write extents that were never merged are moved
to the head of LRU for earlier reclaim, preserving extents with
better continuity and higher read-hit probability.

Signed-off-by: Yongpeng Yang <[email protected]>
---
  fs/f2fs/data.c         |  4 ++--
  fs/f2fs/extent_cache.c | 29 ++++++++++++++++++++++++++++-
  fs/f2fs/f2fs.h         | 14 +++++++++++++-
  fs/f2fs/file.c         |  6 ++++--
  4 files changed, 47 insertions(+), 6 deletions(-)

diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 9c6440a7db0e..2d38135005fe 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -1873,7 +1873,7 @@ int f2fs_map_blocks(struct inode *inode, struct 
f2fs_map_blocks *map, int flag)
f2fs_update_read_extent_cache_range(&dn,
                                start_pgofs, map->m_pblk + ofs,
-                               map->m_len - ofs);
+                               map->m_len - ofs, EX_ACCESS_PRECACHE);
                }
        }
@@ -1919,7 +1919,7 @@ int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map, int flag)
                        if (map->m_len > ofs)
                                f2fs_update_read_extent_cache_range(&dn,
                                        start_pgofs, map->m_pblk + ofs,
-                                       map->m_len - ofs);
+                                       map->m_len - ofs, EX_ACCESS_PRECACHE);
                }
                if (map->m_next_extent)
                        *map->m_next_extent = is_hole ? pgofs + 1 : pgofs;
diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
index 82d84c4e98b2..e141ffb64e5f 100644
--- a/fs/f2fs/extent_cache.c
+++ b/fs/f2fs/extent_cache.c
@@ -142,6 +142,7 @@ static void __try_update_largest_extent(struct extent_tree 
*et,
        if (en->ei.len <= et->largest.len)
                return;
+ en->ei.last_access_mode = EX_ACCESS_LARGEST;
        et->largest = en->ei;
        et->largest_updated = true;
  }
@@ -518,6 +519,7 @@ static bool __lookup_extent_tree(struct inode *inode, 
pgoff_t pgofs,
                stat_inc_rbtree_node_hit(sbi, type);
*ei = en->ei;
+       en->ei.last_access_mode = EX_ACCESS_READ;
        spin_lock(&eti->extent_lock);
        if (!list_empty(&en->list)) {
                list_move_tail(&en->list, &eti->extent_list);
@@ -624,6 +626,21 @@ static struct extent_node *__insert_extent_tree(struct 
f2fs_sb_info *sbi,
/* update in global extent list */
        spin_lock(&eti->extent_lock);
+       /*
+        * 1. For the largest extent, if subsequent writes are not merged into
+        * it, the write path will most likely not use the largest extent_node,
+        * while read requests can still access the mapping through the largest
+        * extent.
+        *
+        * 2. For sparse writes, if the extent length is 1 and no extent merging
+        * occurs, this extent should be reclaimed with higher priority to avoid
+        * evicting extents with better continuity and higher read-hit.
+        */
+       if (et->type == EX_READ && et->cached_en &&
+               (et->cached_en->ei.last_access_mode == EX_ACCESS_LARGEST ||
+                (et->cached_en->ei.len == 1 &&
+                 et->cached_en->ei.last_access_mode == EX_ACCESS_WRITE)))
+               list_move(&et->cached_en->list, &eti->extent_list);
        list_add_tail(&en->list, &eti->extent_list);
        et->cached_en = en;
        spin_unlock(&eti->extent_lock);
@@ -747,6 +764,8 @@ static void __update_extent_tree_range(struct inode *inode,
                if (fofs > dei.fofs && (type != EX_READ ||
                                fofs - dei.fofs >= F2FS_MIN_EXTENT_LEN)) {
                        en->ei.len = fofs - en->ei.fofs;
+                       if (type == EX_READ)
+                               en->ei.last_access_mode = EX_ACCESS_TRUNCATE;
                        prev_en = en;
                        parts = 1;
                }
@@ -761,6 +780,8 @@ static void __update_extent_tree_range(struct inode *inode,
                                        end - dei.fofs + dei.blk, false,
                                        dei.age, dei.last_blocks,
                                        type);
+                               if (type == EX_READ)
+                                       ei.last_access_mode = 
EX_ACCESS_TRUNCATE;
                                en1 = __insert_extent_tree(sbi, et, &ei,
                                                        NULL, NULL, true);
                                next_en = en1;
@@ -770,6 +791,8 @@ static void __update_extent_tree_range(struct inode *inode,
                                        en->ei.blk + (end - dei.fofs), true,
                                        dei.age, dei.last_blocks,
                                        type);
+                               if (type == EX_READ)
+                                       en->ei.last_access_mode = 
EX_ACCESS_TRUNCATE;
                                next_en = en;
                        }
                        parts++;
@@ -808,6 +831,7 @@ static void __update_extent_tree_range(struct inode *inode,
        if (tei->blk) {
                __set_extent_info(&ei, fofs, len, tei->blk, false,
                                  0, 0, EX_READ);
+               ei.last_access_mode = tei->last_access_mode;
                if (!__try_merge_extent_node(sbi, et, &ei, prev_en, next_en))
                        __insert_extent_tree(sbi, et, &ei,
                                        insert_p, insert_parent, leftmost);
@@ -978,6 +1002,7 @@ static void __update_extent_cache(struct dnode_of_data 
*dn, enum extent_type typ
                        ei.blk = NULL_ADDR;
                else
                        ei.blk = dn->data_blkaddr;
+               ei.last_access_mode = EX_ACCESS_WRITE;
        } else if (type == EX_BLOCK_AGE) {
                if (__get_new_block_age(dn->inode, &ei, dn->data_blkaddr))
                        return;
@@ -1091,12 +1116,14 @@ void f2fs_update_read_extent_cache(struct dnode_of_data 
*dn)
  }
void f2fs_update_read_extent_cache_range(struct dnode_of_data *dn,
-                               pgoff_t fofs, block_t blkaddr, unsigned int len)
+                               pgoff_t fofs, block_t blkaddr, unsigned int len,
+                               enum extent_access_mode access_mode)
  {
        struct extent_info ei = {
                .fofs = fofs,
                .len = len,
                .blk = blkaddr,
+               .last_access_mode = access_mode,
        };
if (!__may_extent_tree(dn->inode, EX_READ))
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index fffb516b78f4..1588b64d04a3 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -763,6 +763,15 @@ enum extent_type {
        NR_EXTENT_CACHES,
  };
+/* extent acces mode for cache hit or extent add */
+enum extent_access_mode {
+       EX_ACCESS_READ,
+       EX_ACCESS_WRITE,
+       EX_ACCESS_PRECACHE,
+       EX_ACCESS_TRUNCATE,
+       EX_ACCESS_LARGEST,
+};
+
  /*
   * Reserved value to mark invalid age extents, hence valid block range
   * from 0 to ULLONG_MAX-1
@@ -781,6 +790,8 @@ struct extent_info {
                        /* physical extent length of compressed blocks */
                        unsigned int c_len;
  #endif
+                       /* record last access mode */
+                       enum extent_access_mode last_access_mode;

As we know, memory is expensive, :P, I'd like to know if we can enable this
optionally if there is benefits.

extent_access_mode consumes only padding holes within the union and
brings no additional memory overhead. The union is sized to 16 bytes in
total, whereas blk, c_len and extent_access_mode together take up just
12 bytes.

Thanks
Yongpeng,



Thanks,


                };
                /* block age extent_cache */
                struct {
@@ -4577,7 +4588,8 @@ bool f2fs_lookup_read_extent_cache_block(struct inode 
*inode, pgoff_t index,
                        block_t *blkaddr);
  void f2fs_update_read_extent_cache(struct dnode_of_data *dn);
  void f2fs_update_read_extent_cache_range(struct dnode_of_data *dn,
-                       pgoff_t fofs, block_t blkaddr, unsigned int len);
+                               pgoff_t fofs, block_t blkaddr, unsigned int len,
+                               enum extent_access_mode access_mode);
  unsigned int f2fs_shrink_read_extent_tree(struct f2fs_sb_info *sbi,
                        int nr_shrink);
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 633e9ade654f..a3a5d499eadf 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -733,7 +733,8 @@ void f2fs_truncate_data_blocks_range(struct dnode_of_data 
*dn, int count)
                 */
                fofs = f2fs_start_bidx_of_node(ofs_of_node(dn->node_folio),
                                                        dn->inode) + ofs;
-               f2fs_update_read_extent_cache_range(dn, fofs, 0, len);
+               f2fs_update_read_extent_cache_range(dn, fofs, 0, len,
+                                                       EX_ACCESS_TRUNCATE);
                f2fs_update_age_extent_cache_range(dn, fofs, len);
                dec_valid_block_count(sbi, dn->inode, nr_free);
        }
@@ -1672,7 +1673,8 @@ static int f2fs_do_zero_range(struct dnode_of_data *dn, 
pgoff_t start,
if (index > start) {
                f2fs_update_read_extent_cache_range(dn, start, 0,
-                                                       index - start);
+                                                       index - start,
+                                                       EX_ACCESS_TRUNCATE);
                f2fs_update_age_extent_cache_range(dn, start, index - start);
        }



_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel



_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

Reply via email to