[PATCH v3 0/2] f2fs: pack same-inode blocks by inode during FG_GC
From: Daejun Park
Date: Thu Jul 16 2026 - 02:48:44 EST
Hi,
FG_GC migrates a victim section's valid blocks in source segment-offset
order, so blocks from several inodes stay interleaved in the destination
curseg and post-GC files stay fragmented. This series migrates them in
inode order instead, producing inode-contiguous runs across the section.
Patch 1 is a pure refactor (extract do_migrate_one_data_block()).
Patch 2 records each valid block on a per-inode list in phase 3 and
drains it in inode order via pack_gc_section() once the section has been
scanned. It is gated on a sysfs knob (gc_inode_local_packing) and FG_GC
only; on slab alloc failure a block falls back to immediate migration.
Changes since v2 (all in patch 2; the first three address Chao Yu's
review):
- default gc_inode_local_packing to 0: the feature is now opt-in
rather than enabled on large sections, since a new GC behaviour
should not be on by default.
- drop the section's queued gc_blocks on the freezing 'goto stop' path
(new drop_gc_blocks() helper). f2fs_gc() reuses one gc_list across
the sections it collects in a single call, so leftover records could
otherwise leak into the next section's pack pass.
- expose the packing-queue drain threshold as a sysfs knob
gc_pack_max_blocks (default 4096, minimum 64) instead of a fixed
constant. The ABI doc describes it as a drain threshold checked at
segment boundaries, since the queue can transiently overshoot the
value by up to one segment's blocks.
- pack_gc_section() skips a queued block invalidated since phase 3
(check_valid_map), matching the legacy phase-4 skip, so a stale block
is neither migrated nor stat-counted.
- queued records are freed in one place: pack_gc_section() and
put_gc_inode() release them via drop_gc_blocks() instead of three
copies of the same walk, and the normal-completion pack_gc_section()
call is guarded by nr_gc_blocks so a queue already emptied by an
early drain does not trigger a useless walk of the inode list.
- gc_inode_local_packing's sysfs store assigns the bool directly
(!!t), like iostat_enable/readdir_ra, instead of falling through to
the generic unsigned-int write over a bool field.
- drop the '+45 %' hot-rewrite iteration figure from patch 2's
measurements: it is a wall-clock number and QEMU virtio
bandwidth/latency is not reliable; the structural counters stand.
- patch 1: unchanged.
Rebased onto current f2fs/dev (now includes "f2fs: embed f2fs_gc_kthread
in f2fs_sb_info"); no code change, the diff is identical to the c0b65f612
base it was developed on.
The seg_freed recompute Chao asked about is unchanged from v2: it runs
after the pack pass on all exit paths and counts already-empty segments
exactly as the legacy 'goto freed' path does (required so a fully
reclaimed section reaches seg_freed == f2fs_usable_segs_in_sec();
excluding them would re-introduce the -EAGAIN this series fixes). Any
already-empty segment it counts feeds only the total_freed tracepoint,
not the sec_freed/-EAGAIN decision.
Changes since v1:
- patch 2: fix seg_freed accounting (recompute after the deferred pack
pass); without it a sync F2FS_IOC_GC returned -EAGAIN despite freeing
space. Clear next_victim_seg on the freezing path; bound the packing
queue and drain in batches. Rebased onto f2fs/dev.
Results (QEMU guest):
- large section (-s 32, 64 x 4 MiB): post-GC extents 65536 -> 49170
(-24 %).
- natural FG_GC, 90 % full, 300 s hot/cold rewrite: cold-file extents
-63 %, move_blks -18 %, skipped_gc_rwsem -59 %.
- seg_freed regression test: packing matches the legacy path
(seg_freed 152, sec_freed 38, 24/24 sync ioctls succeed).
- gc_pack_max_blocks partial-drain test (cap=64, large section): the
drain fires ~1.5k times in one section yet seg_freed still matches
the legacy path (384 == 384), integrity preserved.
- regression suite on this tree: clean dmesg, sha256 preserved;
gc_pack_max_blocks validates its bounds and gc_inode_local_packing
defaults to 0.
Daejun Park (2):
f2fs: extract do_migrate_one_data_block() helper for GC migration
f2fs: pack same-inode blocks by inode during FG_GC
Documentation/ABI/testing/sysfs-fs-f2fs | 20 ++
fs/f2fs/f2fs.h | 9 +-
fs/f2fs/gc.c | 302 +++++++++++++++++++-----
fs/f2fs/gc.h | 5 +
fs/f2fs/super.c | 2 +
fs/f2fs/sysfs.c | 16 ++
6 files changed, 297 insertions(+), 57 deletions(-)
base-commit: 6429f3bf6dc33cba42055260f905559c43976eeb
--
2.43.0