mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Chao Yu <chao@kernel.org>
To: daejun7.park@samsung.com,
	"jaegeuk@kernel.org" <jaegeuk@kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	"linux-f2fs-devel@lists.sourceforge.net"
	<linux-f2fs-devel@lists.sourceforge.net>
Cc: chao@kernel.org
Subject: Re: [PATCH v2 2/2] f2fs: pack same-inode blocks by inode during FG_GC
Date: Mon, 29 Jun 2026 20:08:10 +0800	[thread overview]
Message-ID: <e5ed9594-0137-4329-97e4-1e268c3dd91e@kernel.org> (raw)
In-Reply-To: <20260605091310epcms2p7e1130728968307f799403ef00ee38ba6@epcms2p7>

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/<disk>/.  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 <daejun7.park@samsung.com>
> ---
> 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/<disk>/gc_inode_local_packing
> +Date:		May 2026
> +Contact:	Daejun Park <daejun7.park@samsung.com>
> +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 <trace/events/f2fs.h>
>  
>  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),


  reply	other threads:[~2026-06-29 12:08 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <CGME20260605091139epcms2p2b17a6e7962b7c9b8fb058e1c55cd4dba@epcms2p2>
2026-06-05  9:11 ` [PATCH v2 0/2] " Daejun Park
     [not found]   ` <CGME20260605091139epcms2p2b17a6e7962b7c9b8fb058e1c55cd4dba@epcms2p4>
2026-06-05  9:12     ` [PATCH v2 1/2] f2fs: extract do_migrate_one_data_block() helper for GC migration Daejun Park
     [not found]   ` <CGME20260605091139epcms2p2b17a6e7962b7c9b8fb058e1c55cd4dba@epcms2p7>
2026-06-05  9:13     ` [PATCH v2 2/2] f2fs: pack same-inode blocks by inode during FG_GC Daejun Park
2026-06-29 12:08       ` Chao Yu [this message]
     [not found]       ` <CGME20260605091139epcms2p2b17a6e7962b7c9b8fb058e1c55cd4dba@epcms2p3>
2026-06-30 10:23         ` Daejun Park
2026-06-30  4:25     ` Daejun Park

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e5ed9594-0137-4329-97e4-1e268c3dd91e@kernel.org \
    --to=chao@kernel.org \
    --cc=daejun7.park@samsung.com \
    --cc=jaegeuk@kernel.org \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome