On 6/19/26 22:45, Yongpeng Yang wrote:
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.

I think it needs to give some numbers to show benefits.





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.

Well, seems fine now, but I just notice block age extent costs more space
than read extent.

Thanks,


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