From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB580403B1E for ; Mon, 29 Jun 2026 12:08:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782734896; cv=none; b=FH6HhQiDzp6zzuMoKsW3RyYyRQiZ2eKNZHbqj4q1p9o1OJ7vOqBkC+UHmwXwXUgAH7/C3rq8b8A9c1xSDfMtS1bgfzT93m1LVG0Za4qeAkfVXXTs3WlnWDymJ06XNvYe1+Hvyuj94j0aOY7DL3QhfgXEz0Vap/lR8NB9UCT48jk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782734896; c=relaxed/simple; bh=RfKeNhs8IvHOcgx2icfJPuZmxaVtFXlCLpY3Q5W/ox0=; h=Message-ID:Date:MIME-Version:From:Subject:To:Cc:References: In-Reply-To:Content-Type; b=iLeNrEjNB95xWwL4SDwfqdBs6zT+1zgRZFCV1yYNJfaFKiRaFX6p1T8pkFsUgLsKf6piV2kKE25NHrXbr9tCvXF7gQrX9Jq+MScoYQB52i3taYoUcpaK7qjEk3lONxlBHkm1qRlwR91egW6OYJKPvjdgf3e9KzoLS0UDZoim4JI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=M+Y+1ycw; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="M+Y+1ycw" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DE83F1F00A3A; Mon, 29 Jun 2026 12:08:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1782734893; bh=48TQt2moS5iFOKARI4ac+F9KFz7d+DPpWzraA0ySr18=; h=Date:From:Subject:To:Cc:References:In-Reply-To; b=M+Y+1ycw64CZX5gaWi4+O2LGAa37Tr/bsLfiJNYwOcTMt4Ko4/8frnsyyGIj/onh2 yUOOgXJ0kIWbU0wdsnyMUiuhGKaZ0nqxAN1xIS3BDdjnVZHSP4WzMDlYkLizosoC9i 0TRCxmQdVxoBXqU/u5/xuRIXZlWtsSlEwnZ5HVZcukRgIUnHSLvdzEZX0WbJLDJQ7Q o7QfyiGjAmzg2gtj6Cam9ryZz3Fj2us/iIr1E5NSbeCCpRdNv4ZappNgYQvIBfWVA1 qPN8LfbgK1L7VYN7hRWtRCYZavDY/2CyA2wUqXiQu2tqMTqzPcbGqSEG8uB6gHZgUM GXYXWyxkh/DPQ== Message-ID: Date: Mon, 29 Jun 2026 20:08:10 +0800 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: Chao Yu Subject: Re: [PATCH v2 2/2] f2fs: pack same-inode blocks by inode during FG_GC To: daejun7.park@samsung.com, "jaegeuk@kernel.org" , "linux-kernel@vger.kernel.org" , "linux-f2fs-devel@lists.sourceforge.net" Cc: chao@kernel.org References: <20260605091229epcms2p406d0a66d4fa12ba1d77bc668ebcfe352@epcms2p4> <20260605091139epcms2p2b17a6e7962b7c9b8fb058e1c55cd4dba@epcms2p2> <20260605091310epcms2p7e1130728968307f799403ef00ee38ba6@epcms2p7> Content-Language: en-US In-Reply-To: <20260605091310epcms2p7e1130728968307f799403ef00ee38ba6@epcms2p7> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit On 6/5/26 17:13, Daejun Park wrote: > The legacy FG_GC path migrates a victim section's valid blocks in > source segment-offset order: blocks of several inodes that were > interleaved in each source segment are migrated to the destination > curseg in the same interleaved order, carrying source-side > fragmentation forward into the post-GC layout regardless of section > size. > > Pack the migration order by inode for every victim section: > > * gc_data_segment()'s phase 3 records each valid block on a > per-inode gc_blocks list hanging off the inode_entry that > add_gc_inode() already creates in gc_list. Each gc_block > carries the source segno, nofs, ofs_in_node and per-segment off > so the deferred migration can rebuild start_bidx and pass the > correct segno to check_valid_map() inside the existing > do_migrate_one_data_block() helper. > > * Phase 4 of gc_data_segment() is gated by nr_phases: in packing > mode nr_phases caps the loop at 4 (phases 0..3), so the summary > block is not re-scanned just to hit a per-slot 'continue'. The > phase 4 migration body is reached only via the new 'goto > do_migrate' fallback path described below, in which case the > inode_entry just returned by add_gc_inode() is reused instead of > repeating find_gc_inode(). > > * do_garbage_collect() invokes pack_gc_section() once, after every > source segment of the victim section has been parsed. Walking > gc_list->ilist in inode order emits all of one inode's blocks > contiguously to the destination curseg. On large sections this > lets an inode's blocks span the full SEGS_PER_SEC * > usable_blks_in_seg destination range. > > * Because migration is deferred past gc_data_segment()'s > per-segment loop, the in-loop 'freed:' check can no longer > observe the emptied source segments: at that point the source is > still fully valid. do_garbage_collect() therefore skips the > in-loop seg_freed count when packing and, after the pack pass, > recomputes seg_freed across the scanned segment range. Without > this, FG_GC reports seg_freed == 0 for a fully packed section, so > f2fs_gc() never counts the section as reclaimed, has_enough_free_secs() > keeps looping, and a sync F2FS_IOC_GC returns -EAGAIN despite > having freed the space. > > * i_gc_rwsem is taken and released per block inside the packing > pass (via do_migrate_one_data_block), matching the legacy > phase 4 lock-holding window so concurrent user IO sees no > additional latency. > > Activation conditions: > * sbi->gc_inode_local_packing == true (sysfs writable, accepts > only 0 or 1; default derived from __is_large_section(sbi) since > the gain on a single-segment section is marginal and adds memory > pressure with little return) > * gc_type == FG_GC; BG_GC's move_data_page() path defers > destination allocation to the writeback flusher, so any > reordering applied during GC is lost. > > Race against the sysfs knob: gc_inode_local_packing is unsynchronised. > Re-reading it from phase 3 (enqueue), phase 4 (skip) and the pack > pass independently would let a concurrent toggle queue blocks via > gc_blocks and then bypass pack_gc_section(). do_garbage_collect() > snapshots the value into a local 'pack_by_inode' bool and threads it > through gc_data_segment() and the packing call so all three sites > remain consistent for the entire section. > > On the freezing 'goto stop' path the pack pass is skipped; the queued > records are freed un-migrated by put_gc_inode(), and next_victim_seg is > cleared (NULL_SEGNO) so the next FG_GC re-selects a victim by search > rather than resuming past the still-valid source segments whose blocks > were just dropped. > > Per-block records are allocated from a dedicated f2fs_gc_block slab > (via f2fs_kmem_cache_create) rather than kmalloc(GFP_NOFS). To bound > the transient footprint, the queue is drained early once it reaches > MAX_GC_PACK_BLOCKS (4096, ~128 KiB at 32 B per gc_block) instead of > holding an entire section's worth of records before the first write: > ordinary sections stay under the cap and pack in a single pass, while > a pathologically large section drains in batches. A per-cache > slabinfo line and FAULT_SLAB_ALLOC coverage of the fallback path are > useful for diagnostics. > > Allocation failure falls through to 'goto do_migrate', the same > phase 4 body the !pack_by_inode path uses, so the block is migrated > immediately rather than dropped. This costs the packing benefit > for the one block but preserves FG_GC progress under memory > pressure, which matters more when FG_GC is called precisely > because the system is short on free sections. > > Measurements (QEMU virtio guest, 4-cycle fragmentation, gc_urgent > 40s, filefrag total extents before/after GC; structural counters > only since QEMU virtio BW/lat is unreliable): > > Large section (mkfs.f2fs -s 32 = 64 MiB section, > 64 files x 4 MiB): > legacy 65536 -> 65536 0 % reduction > packed 65536 -> 49170 24 % reduction (-16366 extents) > > Default section (mkfs.f2fs -s 1 = 2 MiB section, > 128 files x 256 KiB): > legacy 8192 -> 8192 0 % reduction > packed 8192 -> 7690 6 % reduction > GC work (move_blks, cp_blks, gc_calls) identical between modes; > the packing only reorders dest curseg writes. > > Natural FG_GC under tight cold migration > (mkfs.f2fs -s 32, 2 GiB disk 90 % fill, > 6 hot x 200 MiB + 6 cold x 100 MiB interleaved fill, > background_gc=sync, 300 s hot rewrite): > legacy cold extents 350 -> 357 (delta +7, no improvement) > packed cold extents 350 -> 132 (delta -218, -63 % reduction) > per user iter: > move_blks legacy 42344 packed 34822 (-18 %) > cp_blocks legacy 23.90 packed 22.95 (-4 %) > skipped_gc_rwsem legacy 108 packed 44 (-59 %) > hot rewrite iters in fixed 300 s window: +45 % Can you please explain why we can get above benefits except per-file defragment during FGGC? Not sure, due to in paralell rewriting/removing races w/ fggc on the same section? May be you can provide scripts in somewhere? :) > > seg_freed accounting regression test (QEMU guest, mkfs.f2fs -s 4, > 400 x 1 MiB files written sequentially so each 2 MiB segment holds > exactly two files, every even-indexed file deleted so each data > segment is ~50 % valid with no trivially-empty segment -- forcing > GC to free sections by migration, the path this commit defers). > Forced sync F2FS_IOC_GC x24 plus gc_urgent, packing OFF vs ON: > > metric (sum over FG_GC) packing OFF packing ON > f2fs_gc_end seg_freed 152 152 > f2fs_gc_end sec_freed 38 38 > F2FS_IOC_GC(sync) successes 24 24 > data integrity (sha256) OK OK > > Without the deferred-migration seg_freed recompute the ON column > collapses to seg_freed/sec_freed = 0 and all 24 ioctls return > -EAGAIN, which is the regression this test guards against. > > Sanity verified in QEMU guest (mkfs.f2fs -s 8, 16 x 4 MiB files, > gc_urgent + remount): data sha256 matches before and after GC; no > WARN/BUG in dmesg; gc_inode_local_packing knob exposed under > /sys/fs/f2fs//. An additional stress run on mkfs.f2fs -s 32 > with FAULT_SLAB_ALLOC at inject_rate=4 triggered 7689 slab alloc > failures during FG_GC, exercising the 'goto do_migrate' fallback; > sha256 was preserved and dmesg stayed clean. > > Signed-off-by: Daejun Park > --- > v2: > - recompute seg_freed over the scanned segment range after > pack_gc_section(): the deferred migration meant the in-loop 'freed:' > check saw the source still valid, so seg_freed stayed 0 and a packed > section was never counted as reclaimed (sync F2FS_IOC_GC -> -EAGAIN). > - clear next_victim_seg on the freezing 'goto stop' path, since the > blocks queued for the section are dropped un-migrated there. > - bound the packing queue with MAX_GC_PACK_BLOCKS and drain early once > the cap is hit, instead of holding a whole section's gc_block records. > - rebased onto current f2fs/dev. > > Documentation/ABI/testing/sysfs-fs-f2fs | 10 ++ > fs/f2fs/f2fs.h | 7 +- > fs/f2fs/gc.c | 162 ++++++++++++++++++++++-- > fs/f2fs/gc.h | 1 + > fs/f2fs/super.c | 1 + > fs/f2fs/sysfs.c | 7 + > 6 files changed, 176 insertions(+), 12 deletions(-) > > diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs > index 1b58c029a..1085af8f6 100644 > --- a/Documentation/ABI/testing/sysfs-fs-f2fs > +++ b/Documentation/ABI/testing/sysfs-fs-f2fs > @@ -1002,3 +1002,13 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck > threads, limitation as below: > - it requires user has CAP_SYS_NICE capability. > - the range is [100, 139], by default the value is 120. > + > +What: /sys/fs/f2fs//gc_inode_local_packing > +Date: May 2026 > +Contact: Daejun Park > +Description: When set to 1, foreground GC packs valid blocks of the same > + inode contiguously into the destination curseg, in addition to > + (rather than within) source segment-offset order. Effective > + only under FG_GC; BG_GC's writeback-deferred destination > + allocation is unaffected. Default is 1 on large sections > + (SEGS_PER_SEC > 1), 0 otherwise. Set to 0 to disable. > diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h > index 9f24287de..5d5fe6d98 100644 > --- a/fs/f2fs/f2fs.h > +++ b/fs/f2fs/f2fs.h > @@ -404,8 +404,9 @@ struct ino_entry { > > /* for the list of inodes to be GCed */ > struct inode_entry { > - struct list_head list; /* list head */ > - struct inode *inode; /* vfs inode pointer */ > + struct list_head list; /* list head */ > + struct inode *inode; /* vfs inode pointer */ > + struct list_head gc_blocks; /* per-inode block list for GC packing */ > }; > > struct fsync_node_entry { > @@ -1907,6 +1908,8 @@ struct f2fs_sb_info { > unsigned int migration_granularity; > /* migration window granularity of garbage collection, unit: segment */ > unsigned int migration_window_granularity; > + /* pack same-inode blocks together during FG_GC migration */ > + bool gc_inode_local_packing; > > /* > * for stat information. > diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c > index e232dff72..af58ebfc9 100644 > --- a/fs/f2fs/gc.c > +++ b/fs/f2fs/gc.c > @@ -24,6 +24,24 @@ > #include > > static struct kmem_cache *victim_entry_slab; > +static struct kmem_cache *gc_block_slab; > + > +/* Per-block migration record for inode-local packing under FG_GC. */ > +struct gc_block { > + struct list_head list; > + unsigned int segno; /* source segment for check_valid_map() */ > + unsigned int nofs; > + unsigned int ofs_in_node; > + int off; > +}; > + > +/* > + * Upper bound on blocks held on the per-inode packing queue before a > + * partial drain. Caps the transient gc_block slab footprint on very > + * large victim sections; ordinary sections stay under it and pack in a > + * single pass. > + */ > +#define MAX_GC_PACK_BLOCKS 4096 What do you think of introducing a sysfs for this parameter? > > static unsigned int count_bits(const unsigned long *addr, > unsigned int offset, unsigned int len); > @@ -1004,6 +1022,7 @@ static struct inode_entry *add_gc_inode(struct gc_inode_list *gc_list, > new_ie = f2fs_kmem_cache_alloc(f2fs_inode_entry_slab, > GFP_NOFS, true, NULL); > new_ie->inode = inode; > + INIT_LIST_HEAD(&new_ie->gc_blocks); > > f2fs_radix_tree_insert(&gc_list->iroot, inode->i_ino, new_ie); > list_add_tail(&new_ie->list, &gc_list->ilist); > @@ -1013,8 +1032,13 @@ static struct inode_entry *add_gc_inode(struct gc_inode_list *gc_list, > static void put_gc_inode(struct gc_inode_list *gc_list) > { > struct inode_entry *ie, *next_ie; > + struct gc_block *e, *tmp_e; > > list_for_each_entry_safe(ie, next_ie, &gc_list->ilist, list) { > + list_for_each_entry_safe(e, tmp_e, &ie->gc_blocks, list) { > + list_del(&e->list); > + kmem_cache_free(gc_block_slab, e); > + } > radix_tree_delete(&gc_list->iroot, ie->inode->i_ino); > iput(ie->inode); > list_del(&ie->list); > @@ -1646,7 +1670,7 @@ static int do_migrate_one_data_block(struct f2fs_sb_info *sbi, > */ > static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > struct gc_inode_list *gc_list, unsigned int segno, int gc_type, > - bool force_migrate, struct blk_plug *plug) > + bool force_migrate, bool pack_by_inode, struct blk_plug *plug) > { > struct super_block *sb = sbi->sb; > struct f2fs_summary *entry; > @@ -1655,6 +1679,8 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > int phase = 0; > int submitted = 0; > unsigned int usable_blks_in_seg = f2fs_usable_blks_in_seg(sbi, segno); > + /* packing path skips phase 4; pack_gc_section() handles migration */ > + int nr_phases = pack_by_inode ? 4 : 5; > > start_addr = START_BLOCK(sbi, segno); > > @@ -1663,6 +1689,7 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > > for (off = 0; off < usable_blks_in_seg; off++, entry++) { > struct inode *inode; > + struct inode_entry *ie = NULL; > struct node_info dni; /* dnode info for the data */ > unsigned int ofs_in_node, nofs; > block_t start_bidx; > @@ -1705,6 +1732,7 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > > if (phase == 3) { > struct folio *data_folio; > + struct gc_block *e; > int err; > > inode = f2fs_iget(sb, dni.ino); > @@ -1751,8 +1779,10 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > iput(inode); > continue; > } > - add_gc_inode(gc_list, inode); > - continue; > + ie = add_gc_inode(gc_list, inode); > + if (!pack_by_inode) > + continue; > + goto queue; > } > > data_folio = f2fs_get_read_data_folio(inode, start_bidx, > @@ -1764,18 +1794,38 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > } > > f2fs_folio_put(data_folio, false); > - add_gc_inode(gc_list, inode); > + ie = add_gc_inode(gc_list, inode); > + if (!pack_by_inode) > + continue; > +queue: > + e = f2fs_kmem_cache_alloc(gc_block_slab, GFP_NOFS, > + false, sbi); > + if (!e) > + goto do_migrate; /* alloc fail: migrate now */ > + e->segno = segno; > + e->nofs = nofs; > + e->ofs_in_node = ofs_in_node; > + e->off = off; > + list_add_tail(&e->list, &ie->gc_blocks); > + gc_list->nr_gc_blocks++; > continue; > } > > - /* phase 4 */ > - inode = find_gc_inode(gc_list, dni.ino); > + /* > + * phase 4: legacy per-segment migration. Capped out by > + * nr_phases when packing is on; reached only via the > + * 'goto do_migrate' fallback above, in which case @ie is > + * the entry add_gc_inode() just returned and we reuse it > + * instead of repeating the radix-tree lookup. > + */ > +do_migrate: > + inode = ie ? ie->inode : find_gc_inode(gc_list, dni.ino); > if (inode) > submitted += do_migrate_one_data_block(sbi, inode, > segno, off, nofs, ofs_in_node, gc_type); > } > > - if (++phase < 5) { > + if (++phase < nr_phases) { > blk_finish_plug(plug); > blk_start_plug(plug); > goto next_step; > @@ -1784,6 +1834,32 @@ static int gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, > return submitted; > } > > +/* > + * pack_gc_section - migrate all gc_blocks queued for this victim section, > + * grouped by inode. gc_list->ilist is walked in insertion order so > + * destination curseg writes form inode-contiguous runs that span every > + * source segment of the section. > + */ > +static int pack_gc_section(struct f2fs_sb_info *sbi, > + struct gc_inode_list *gc_list, int gc_type) > +{ > + struct inode_entry *ie; > + struct gc_block *e, *tmp; > + int submitted = 0; > + > + list_for_each_entry(ie, &gc_list->ilist, list) { > + list_for_each_entry_safe(e, tmp, &ie->gc_blocks, list) { > + submitted += do_migrate_one_data_block(sbi, ie->inode, > + e->segno, e->off, e->nofs, > + e->ofs_in_node, gc_type); > + list_del(&e->list); > + kmem_cache_free(gc_block_slab, e); > + } > + } > + gc_list->nr_gc_blocks = 0; > + return submitted; > +} > + > static int __get_victim(struct f2fs_sb_info *sbi, unsigned int *victim, > int gc_type, bool one_time) > { > @@ -1810,6 +1886,13 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, > unsigned char type; > unsigned char data_type; > int submitted = 0, sum_blk_cnt; > + /* > + * Snapshot the packing knob once for this section. Re-reading the > + * sysfs-writable bool from phase 3, phase 4 and the pack pass would > + * let a concurrent toggle queue blocks via add_gc_block() and then > + * bypass pack_gc_section(), losing this cycle of migration. > + */ > + bool pack_by_inode = sbi->gc_inode_local_packing && gc_type == FG_GC; > > if (__is_large_section(sbi)) { > sec_end_segno = rounddown(end_segno, SEGS_PER_SEC(sbi)); > @@ -1938,14 +2021,20 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, > else > submitted += gc_data_segment(sbi, sum->entries, > gc_list, cur_segno, > - gc_type, force_migrate, &plug); > + gc_type, force_migrate, > + pack_by_inode, &plug); > > stat_inc_gc_seg_count(sbi, data_type, gc_type); > sbi->gc_reclaimed_segs[sbi->gc_mode]++; > migrated++; > > freed: > - if (gc_type == FG_GC && > + /* > + * Packing defers migration to pack_gc_section() after > + * this loop, so the source segment is still fully valid > + * here; seg_freed is recomputed below the stop: label. > + */ > + if (!pack_by_inode && gc_type == FG_GC && > get_valid_blocks(sbi, cur_segno, false) == 0) > seg_freed++; > > @@ -1956,20 +2045,64 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, > > if (unlikely(freezing(current))) { > folio_put_refs(sum_folio, 2); > + /* > + * Packing deferred this section's migration to > + * pack_gc_section(), which the goto stop below > + * skips, so blocks queued but not yet drained > + * are dropped un-migrated. Clear the in-section > + * victim hint so the next FG_GC re-selects via a > + * clean search instead of skipping the still > + * valid source segments. > + */ > + if (pack_by_inode && __is_large_section(sbi)) > + sbi->next_victim_seg[gc_type] = NULL_SEGNO; > goto stop; > } > + > + /* > + * Bound the transient gc_block footprint on very large > + * sections: drain once the packing queue grows past > + * MAX_GC_PACK_BLOCKS rather than holding the whole > + * section before migrating anything. > + */ > + if (pack_by_inode && > + gc_list->nr_gc_blocks >= MAX_GC_PACK_BLOCKS) > + submitted += pack_gc_section(sbi, gc_list, > + gc_type); > } > next_block: > folio_put_refs(sum_folio, 2); > segno = block_end_segno; > } > > + /* > + * Drain whatever is still queued for this section. Skipped on the > + * freezing 'goto stop' path: leftover entries are freed un-migrated > + * by put_gc_inode() in f2fs_gc(). > + */ > + if (pack_by_inode) > + submitted += pack_gc_section(sbi, gc_list, gc_type); > + > stop: Should we drop all items in gc_block list belong to current sections for anyone jumps to 'stop' label? so that in next round of section migration, we can avoid to touch block list in previous section. > if (submitted) > f2fs_submit_merged_write(sbi, data_type); > > blk_finish_plug(&plug); > > + /* > + * Packing deferred migration past the per-segment loop, so the > + * in-loop freed: check could not observe the emptied source segments > + * (and on the freezing path some queued blocks were dropped). Count > + * the segments that are actually free now over the scanned range. > + */ > + if (pack_by_inode) { > + unsigned int seg; > + > + for (seg = start_segno; seg < end_segno; seg++) > + if (get_valid_blocks(sbi, seg, false) == 0) > + seg_freed++; Is it possible there is empty segment previously? we should not account it into seg_freed? > + } > + > if (migrated) > stat_inc_gc_sec_count(sbi, data_type, gc_type); > > @@ -2139,11 +2272,20 @@ int __init f2fs_create_garbage_collection_cache(void) > { > victim_entry_slab = f2fs_kmem_cache_create("f2fs_victim_entry", > sizeof(struct victim_entry)); > - return victim_entry_slab ? 0 : -ENOMEM; > + if (!victim_entry_slab) > + return -ENOMEM; > + gc_block_slab = f2fs_kmem_cache_create("f2fs_gc_block", > + sizeof(struct gc_block)); > + if (!gc_block_slab) { > + kmem_cache_destroy(victim_entry_slab); > + return -ENOMEM; > + } > + return 0; > } > > void f2fs_destroy_garbage_collection_cache(void) > { > + kmem_cache_destroy(gc_block_slab); > kmem_cache_destroy(victim_entry_slab); > } > > diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h > index 6c4d45675..f0541d0b7 100644 > --- a/fs/f2fs/gc.h > +++ b/fs/f2fs/gc.h > @@ -75,6 +75,7 @@ struct f2fs_gc_kthread { > struct gc_inode_list { > struct list_head ilist; > struct radix_tree_root iroot; > + unsigned int nr_gc_blocks; /* blocks queued for inode-local packing */ > }; > > struct victim_entry { > diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > index 87f816f01..d2dc5a2b6 100644 > --- a/fs/f2fs/super.c > +++ b/fs/f2fs/super.c > @@ -4363,6 +4363,7 @@ static void init_sb_info(struct f2fs_sb_info *sbi) > sbi->migration_granularity = SEGS_PER_SEC(sbi); > sbi->migration_window_granularity = f2fs_sb_has_blkzoned(sbi) ? > DEF_MIGRATION_WINDOW_GRANULARITY_ZONED : SEGS_PER_SEC(sbi); > + sbi->gc_inode_local_packing = __is_large_section(sbi); I don't think we should enable a new feature by default, due to it brings bugs commonly. Thanks, > sbi->seq_file_ra_mul = MIN_RA_MUL; > sbi->max_fragment_chunk = DEF_FRAGMENT_SIZE; > sbi->max_fragment_hole = DEF_FRAGMENT_SIZE; > diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c > index 665687244..30a3beb60 100644 > --- a/fs/f2fs/sysfs.c > +++ b/fs/f2fs/sysfs.c > @@ -659,6 +659,11 @@ static ssize_t __sbi_store(struct f2fs_attr *a, > return -EINVAL; > } > > + if (!strcmp(a->attr.name, "gc_inode_local_packing")) { > + if (t > 1) > + return -EINVAL; > + } > + > if (!strcmp(a->attr.name, "gc_urgent")) { > if (t == 0) { > sbi->gc_mode = GC_NORMAL; > @@ -1269,6 +1274,7 @@ F2FS_SBI_RW_ATTR(gc_reclaimed_segments, gc_reclaimed_segs); > F2FS_SBI_GENERAL_RW_ATTR(max_victim_search); > F2FS_SBI_GENERAL_RW_ATTR(migration_granularity); > F2FS_SBI_GENERAL_RW_ATTR(migration_window_granularity); > +F2FS_SBI_GENERAL_RW_ATTR(gc_inode_local_packing); > F2FS_SBI_GENERAL_RW_ATTR(dir_level); > F2FS_SBI_GENERAL_RW_ATTR(allocate_section_hint); > F2FS_SBI_GENERAL_RW_ATTR(allocate_section_policy); > @@ -1438,6 +1444,7 @@ static struct attribute *f2fs_attrs[] = { > ATTR_LIST(max_victim_search), > ATTR_LIST(migration_granularity), > ATTR_LIST(migration_window_granularity), > + ATTR_LIST(gc_inode_local_packing), > ATTR_LIST(dir_level), > ATTR_LIST(ram_thresh), > ATTR_LIST(ra_nid_pages),