Re: [f2fs-dev] [PATCH RESEND 4/5] f2fs: add extent_access_mode to track extent cache access patterns
Yongpeng Yang <[email protected]>
| Newsgroups | net.sourceforge.lists.linux-f2fs-devel |
|---|---|
| Message-ID | <SEZPR02MB5662354CD0B8CC9D2F56CC1F99E02@SEZPR02MB5662.apcprd02.prod.outlook.com> |
On 6/20/26 2:58 PM, Chao Yu via Linux-f2fs-devel wrote: > 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. OK, I'll try to profile and show the benefits. Thanks Yongpeng, > >> >> >>> >>>> >>>> 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 _______________________________________________ Linux-f2fs-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel