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;