cleaning 是append文件系统中经常使用术语,和trim有联系,但也须要注意他们之间的区别和联系:前端
cleaning: LFS 删去无效数据的过程
trim: SSD 内部删把dirty块设置成invalid,以备后续使用的过程node
鉴于以前评测的nvme SSD场景下 f2fs 总体性能表现出色,有必要分析其背后的根本缘由。f2fs做为一种append模式的文件系统,
cleaning流程的设计和实现起着影响性能的重要的做用。为此有必要深刻了解f2fs的GC的原理和实现。算法
在磁盘格式化成f2fs的时候,会作一遍全盘trim。app
触发时机ide
/* * [GC triggering condition] * 0. GC is not conducted currently. * 1. There are enough dirty segments. * 2. IO subsystem is idle by checking the # of writeback pages. * 3. IO subsystem is idle by checking the # of requests in * bdev's request list. * * Note) We have to avoid triggering GCs frequently. * Because it is possible that some segments can be * invalidated soon after by user update or deletion. * So, I'd like to wait some time to collect dirty segments. */
后台GC机制的触发时机:性能
/* * Background GC is triggered with the following conditions. * 1. There are a number of invalid blocks. * 2. There is not enough free space. */
响应机制测试
当free sections不够的时候:会触发 foreground cleaning spa
src的选择线程
每次选择valid blocks 数量最小的section, 用在foreground cleaning,
目的是为了减少latency(由于须要搬迁的valid数据量少);设计
分状况进行处理:
若是是foreground cleaning:
从parent node 的索引里获取一个新的free log,而后把上面选定的section中valid blocks数据搬移到free log里面去;
并非立刻触发IO。而是把上面选定的section中valid blocks数据读到page cache,而且设置为dirty,这样能合并小块的写,
而后让page cache周期刷到新的空闲块上。
f2fs GC的特点
上面操做作完以后,上面选定的section被标记为pre-free, 只有等到checkpoint作完以后,上面的状态才变为free,能够被再次
分配出去。缘由以下:若是没有作checkpoint 就直接把那个块释放掉,若是它被别人使用以后忽然掉电以后再次拉起来,以前的数据会丢。
为了统一cost-best 和greedy 策略,f2fs 为这两种方法都提供了get_cost接口,分别实现以下:
优先选择cost成本低的segment去作GC:
cost 是segment中有效数据block的数量
static inline unsigned int get_gc_cost(struct f2fs_sb_info *sbi, unsigned int segno, struct victim_sel_policy *p) { if (p->alloc_mode == ×××) return get_seg_entry(sbi, segno)->ckpt_valid_blocks; /* alloc_mode == LFS */ if (p->gc_mode == GC_GREEDY) return get_valid_blocks(sbi, segno, true); else return get_cb_cost(sbi, segno); }
计算比较复杂: 基于时间局部性原理,最近更新的可能立刻还有更新,为此下降其作GC的优先级,对应提升其作GC cost;反之,
对应下降其作GC cost。
static unsigned int get_cb_cost(struct f2fs_sb_info *sbi, unsigned int segno) { struct sit_info *sit_i = SIT_I(sbi); unsigned int secno = GET_SEC_FROM_SEG(sbi, segno); unsigned int start = GET_SEG_FROM_SEC(sbi, secno); unsigned long long mtime = 0; unsigned int vblocks; unsigned char age = 0; unsigned char u; unsigned int i; for (i = 0; i < sbi->segs_per_sec; i++) mtime += get_seg_entry(sbi, start + i)->mtime; vblocks = get_valid_blocks(sbi, segno, true); mtime = div_u64(mtime, sbi->segs_per_sec); vblocks = div_u64(vblocks, sbi->segs_per_sec); u = (vblocks * 100) >> sbi->log_blocks_per_seg; /* Handle if the system time has changed by the user */ if (mtime < sit_i->min_mtime) sit_i->min_mtime = mtime; if (mtime > sit_i->max_mtime) sit_i->max_mtime = mtime; if (sit_i->max_mtime != sit_i->min_mtime) age = 100 - div64_u64(100 * (mtime - sit_i->min_mtime), sit_i->max_mtime - sit_i->min_mtime); return UINT_MAX - ((100 * (100 - u) * age) / (100 + u)); } 上面gc 策略计算公式 UINT_MAX - ((100 * (100 - u) * age) / (100 + u)) 的说明以下: u: 单个block内部的使用率(有效块的数量占block内部全部块的比例) age: 当前block的访问时间距离全部block最近一次访问的时间间隔(sit_i->max_mtime - mtime),在全部block最久远和最近访问时间间隔(sit_i->max_mtime - sit_i->min_mtime)中 的分为占比百分数 cost : UINT_MAX - ((100 * (100 - u) * age) / (100 + u)) u固定的状况下,距离最近访问的时间越久远(age越大),cost 值越小,越可能被选中; age固定的状况下,上述公式等价于 2u/(1+u),意味着 腾挪数据越陈本越低的block 越优先被选取。 参考: Greedy算法 固件须要维护一张Block属性表,记录每一个Block当前的Valid Page数量。假设每次GC处理8个Block,查表挑出Valid Page最少的8个Block进行GC,这样作的好处是复制Valid Page的开销最小。 Cost-Benefit算法 u表明valid page在该Block中的比例,age表明该Block距离最近一次修改的时间。 1-u是对这个Block进行GC之后可以得到Free Page的数量 2u是对这个Block进行GC的开销,读取Valid Page(1个u)而后写入到新的Block(再1个u) (1-u)/2u能够理解为产出投入比 固件须要维护的Block属性表里,须要记录每一个Block最后一次被写入的时间,GC时选择更久没有被修改的Block(冷数据) 该策略就是选择投入产出比更高,未修改时间更长的Block进行GC,二者相乘数字更大的优先被GC CAT算法 CAT的全称是Cost Age Times,在Benefit-Cost算法的基础上,增长了对数据寿命和擦除次数的考虑。 μ表明一个Block里Valid Page的比例; μ/(1-μ)理解为为了释放出(1-μ)的free page必须付出迁移μ的valid page,也就是总体的Cost; 1/age表明Hot degree跟Age成反比 NumberOfCleaning表明Hot degree跟Block的PE Cycle成正比 对每一个Block进行计算,选择那些结果最高的Block进行GC过程。
须要前端GC配合,寻找得哪些segment须要被trim.
GC 也不能太频繁,由于有的数据可能会很快被无效掉(发生了覆盖写或者删除操做)