set_max_huge_pages
写 nr_hugepages
alloc_pool_huge_folio()
逐个 1G folio
alloc_contig_pages()
找连续、可隔离的物理区间
pfn_range_valid_contig()
逐页验证区间是否有效
87% 的耗时
alloc_contig_range()
隔离 + 迁移
replace_free_hugepage_folios()
区间被巨页占住时,先替换空闲巨页
fallback 路径
114G 空闲内存 · 120×1G HugeTLB folios · 104 个成功返回
3.605s
单次分配
87% 耗时在区间验证
pfn_range_valid_contig() 的核心循环
for (i = start_pfn; i < end_pfn; i++) {
page = pfn_to_online_page(i);
if (!page) return false;
if (page_zone(page) != z) return false;
if (PageReserved(page)) return false;
...
}
每个 pfn 都要走一遍
THP 的每个 subpage 也要走
pfn_to_online_page
profile 最大热点
87%
在这段验证里
被浪费的迭代
不在 LRU 上的 THP
高阶 buddy 页
一个整页 / 整块,却按单页走
pfn_to_online_page 被反复调用
验证循环是 87% 热点,也是收益最大的改动
bool page_is_unmovable(zone, page, mode, &step)
step 是输出参数:这一整段可以跳多少页
从页面隔离逻辑抽出
has_unmovable_pages() → page_is_unmovable()
判别结果
PageReserved
→ true
ZONE_MOVABLE
→ false
Compound 非 hugetlb, !LRU
→ true
Compound, movable
→ step = nr_pages - idx
PageBuddy
→ step = 1 << order
PageLRU / movable_ops
→ false
step 的意义
下一次循环从
start_pfn + step
继续,而不是 +1
可移动 THP:一次跨过整个复合页
buddy:一次跨过 2^order 页
tail 页不再被逐个访问
旧:start_pfn++
每个 pfn 都调用一次
pfn_to_online_page()
THP 的每个 subpage 也逐个走
新:start_pfn += step
不可移动复合页:一次判定就拒绝
可移动复合页 / buddy:step 整段跨过
只有真正可能成型的区间才逐页看
pfn_range_valid_contig()
while (start_pfn < end_pfn) {
step = 1;
page = pfn_to_online_page(start_pfn);
if (page_is_unmovable(z, page, ..., &step))
return false;
...
start_pfn += step;
}
区间验证优化后
0.602s
基线 3.605s
−83%
耗时降幅
replace_free_hugepage_folios(start_pfn, end_pfn)
区间被巨页占住时的 fallback
先看有没有可替换的巨页
for_each_hstate(h) {
if (hstate_is_gigantic(h)) continue;
nr += h->free_huge_pages;
if (nr) break;
}
if (!nr) return 0;
gigantic 巨页永不替换
只统计非 gigantic 的 free_huge_pages
有才继续,没有就整个返回
if (order_is_gigantic(folio_order(folio)))
ret = -ENOMEM;
拦住对 gigantic 的误碰
同样的跨段思路
compound → folio_nr_pages - page_idx;buddy → 1 << buddy_order
0.602s → 0.431s
再叠加替换路径优化
hugetlb_cma_alloc_frozen_folio()
if (!hugetlb_cma_size)
return NULL;
启动时没配 CMA 区域 → 直接返回
不做 gfp 试探,也不遍历 nodemask
省掉的空转
gfp_mask 试探
nodemask 遍历
逐节点 hugetlb_cma[] 检查
在无 CMA 的机器上全是白费
标成启动后只读
hugetlb_cma __ro_after_init
hugetlb_cma_only __ro_after_init
初始化时赋值一次,之后不再改变
为什么安全
两个量只在 init 阶段写入
运行期没有写者
运行期写入会触发 fault
114G 空闲内存 · 120×1G HugeTLB folios · 104 个成功返回
分配耗时(秒)
基线
3.605
区间验证后
0.602
再叠加替换优化
0.431
相对基线
−83%
区间验证后
−88%
再叠加替换优化
≈ 8×
总提速
120 个 1G HugeTLB folio:3.605s → 0.431s
四处同一条原则
抽出判别
page_is_unmovable()
按段跳过
start_pfn += step
源头短路
没巨页 / 没 CMA 就返回
启动后只读
__ro_after_init
能在更早处判定无效,就尽早跳过
把时间留给真正可能成型的连续区间
跨过整段无效页,或在源头短路
3.605s
→
0.431s
≈ 8×
120×1G HugeTLB folios · 104 个成功返回
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x