11.1 典型性能问题
11.1.1 重试风暴(Retry Storm)
问题现象:
// drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c
int amdgpu_hmm_range_get_pages(...)
{
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
retry:
hmm_range->notifier_seq = mmu_interval_read_begin(notifier);
r = hmm_range_fault(hmm_range);
if (r == –EBUSY && !time_after(jiffies, timeout))
goto retry; // 频繁重试直到超时
return r == –EBUSY ? –EAGAIN : r;
}
典型场景:
应用程序行为 内核行为 结果
──────────────────────────────────────────────────────────────
GPU频繁访问大内存区域 -> 持续触发page fault -> 大量hmm_range_fault
同时主机内存压力大 -> kswapd频繁回收页面 -> 大量invalidate
↓
hmm_range_fault与invalidate持续冲突
↓
重试率 > 80%,延迟 > 500ms
根本原因:
// mm/hmm.c
int hmm_range_fault(struct hmm_range *range)
{
/* 问题1: 大范围遍历耗时长(10-100ms) */
ret = walk_page_range(mm, start, end, &hmm_walk_ops, &hmm_vma_walk);
/* 问题2: 遍历期间任何invalidate都会导致-EBUSY */
if (mmu_interval_check_retry(range->notifier, range->notifier_seq))
return –EBUSY; // 前功尽弃,从头重试
}
性能影响:
- GPU利用率下降 50-90%
- 应用吞吐量下降 10x-100x
- CPU开销增加(无效的页表遍历)
11.1.2 锁竞争(Lock Contention)
问题现象:
# perf记录锁竞争
$ perf record -e lock:contention_begin -ag — sleep 10
$ perf report
# 典型输出
98.5% [kernel] kvm_mmu_notifier_invalidate_range_start
|–95.2%– spin_lock(&kvm->mmu_lock) # <- 热点
|–3.1%– kvm_unmap_gfn_range
└──1.2%– spin_unlock
触发条件:
// virt/kvm/kvm_main.c
static int kvm_mmu_notifier_invalidate_range_start(...)
{
struct kvm *kvm = mmu_notifier_to_kvm(mn);
/* 多个vCPU同时page fault时竞争此锁 */
spin_lock(&kvm->mmu_lock);
kvm->mn_active_invalidate_count++;
kvm_unmap_gfn_range(kvm, range); // 耗时操作
spin_unlock(&kvm->mmu_lock);
return 0;
}
影响范围:
- 多vCPU虚拟机(≥16 vCPU)
- 内存密集型工作负载
- 大页面映射场景(1GB huge pages)
11.1.3 设备TLB刷新开销
硬件开销:
// drivers/gpu/drm/amd/amdgpu/amdgpu_mn.c
static bool amdgpu_mn_invalidate_gfx(struct mmu_interval_notifier *mni, ...)
{
/* 典型GPU TLB flush延迟 */
amdgpu_vm_invalidate_tlbs(adev);
/*
* MMIO写入: ~1us
* GPU响应: 10-100us
* 等待完成: 可能500us+
*
* 问题: 在spinlock临界区内等待 -> 阻塞其他CPU
*/
}
放大效应:
单次munmap(4KB) -> invalidate(4KB) -> GPU flush整个TLB (100us)
↓
如果是频繁的小范围操作,开销被放大1000x
11.2 性能诊断方法
11.2.1 快速诊断流程
步骤1: 观察现象
│
├─> 应用程序卡住/慢?
│ └─> 检查是否超时: dmesg | grep "hmm.*timeout"
│
├─> GPU利用率低?
│ └─> 检查是否重试: perf trace -e 'mmu_notifier:*'
│
└─> CPU占用高?
└─> 检查热点函数: perf top
步骤2: 定位瓶颈
│
├─> 如果看到大量retry -> 11.3.1 重试优化
├─> 如果看到spin_lock -> 11.3.2 锁优化
└─> 如果看到device_* -> 11.3.3 设备优化
步骤3: 验证修复
└─> 对比优化前后指标(见11.2.3)
11.2.2 关键监控指标
添加统计代码:
// 在驱动中添加debugfs统计
struct mmu_notifier_stats {
atomic64_t invalidate_count; // invalidate总数
atomic64_t retry_count; // hmm_range_fault重试次数
atomic64_t timeout_count; // 超时次数
atomic64_t avg_latency_us; // 平均延迟
};
// 计算关键比率
static void show_stats(struct seq_file *m, struct mmu_notifier_stats *s)
{
u64 inv = atomic64_read(&s->invalidate_count);
u64 retry = atomic64_read(&s->retry_count);
u64 timeout = atomic64_read(&s->timeout_count);
seq_printf(m, "retry_rate: %llu%%\\n", inv ? retry * 100 / inv : 0);
seq_printf(m, "timeout_rate: %llu%%\\n", inv ? timeout * 100 / inv : 0);
seq_printf(m, "avg_latency: %llu us\\n",
atomic64_read(&s->avg_latency_us));
}
健康指标阈值:
指标 正常范围 警告阈值 严重阈值
─────────────────────────────────────────────────────
retry_rate < 5% 5-15% > 15%
timeout_rate < 0.1% 0.1-1% > 1%
avg_latency < 100us 100-500us > 500us
invalidate_freq < 1000/s 1K-10K/s > 10K/s
11.2.3 Tracepoint实战
启用追踪:
# 追踪所有mmu_notifier事件
cd /sys/kernel/debug/tracing
echo 1 > events/mmu_notifier/enable
echo 1 > options/funcgraph-tail
# 查看实时输出
cat trace_pipe
# 典型输出解读
kswapd-100 [002] 123.456: mmu_notifier_invalidate_range_start:
start=0x7f0000 end=0x7f1000
kswapd-100 [002] 123.556: mmu_notifier_invalidate_range_end:
start=0x7f0000 end=0x7f1000 <– 100us延迟
app-1000 [004] 123.457: hmm_range_fault: ret=-EBUSY <– 冲突!
app-1000 [004] 123.557: hmm_range_fault: ret=-EBUSY <– 重试1
app-1000 [004] 123.657: hmm_range_fault: ret=0 <– 成功
分析脚本:
#!/bin/bash
# 分析trace日志,统计重试率
cat trace | awk '
/hmm_range_fault.*-EBUSY/ { retry++ }
/hmm_range_fault.*ret=0/ { success++ }
END {
total = retry + success
print "Total:", total
print "Retry:", retry, "(" retry*100/total "%)"
print "Success:", success, "(" success*100/total "%)"
}'
11.3 优化策略
11.3.1 降低重试率
策略1: 减小遍历粒度
// 优化前: 一次性遍历1GB
hmm_range.start = 0x10000000;
hmm_range.end = 0x50000000; // 1GB范围
hmm_range_fault(&hmm_range); // 遍历耗时100ms -> 极易冲突
// 优化后: 分段遍历
#define CHUNK_SIZE (2UL << 20) // 2MB per chunk
for (addr = start; addr < end; addr += CHUNK_SIZE) {
hmm_range.start = addr;
hmm_range.end = min(addr + CHUNK_SIZE, end);
retry:
hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
ret = hmm_range_fault(&hmm_range); // 遍历仅2ms -> 冲突减少50x
if (ret == –EBUSY && !timeout)
goto retry;
}
// 效果: retry_rate 从 60% 降至 5%
策略2: 提前检测冲突
// 在耗时操作中周期性检查
int device_map_pages(struct device *dev, unsigned long *pfns, int nr)
{
int i;
for (i = 0; i < nr; i++) {
/* 每256个页面检查一次 */
if ((i & 0xFF) == 0) {
if (mmu_interval_check_retry(notifier, seq))
return –EBUSY; // 提前终止,节省90%无效工作
}
device_map_single_page(dev, pfns[i]);
}
}
效果对比:
场景: 遍历10万页 + 映射到设备 (总耗时100ms)
优化前:
– 遍历完成后才check_retry
– 冲突时100ms工作全部作废
– 重试10次 -> 总延迟1秒
优化后:
– 每遍历256页check一次 (每次仅0.25ms)
– 冲突时平均仅作废50ms工作
– 重试3次 -> 总延迟150ms
延迟降低: 1000ms -> 150ms (6.7x提升)
11.3.2 减少锁竞争
策略1: 缩小临界区
// 优化前: 在锁内做所有工作
spin_lock(&dev->lock);
list_for_each_entry(mapping, &dev->mappings, list) {
if (overlaps(mapping, range))
device_unmap(dev, mapping); // 耗时操作
}
spin_unlock(&dev->lock);
// 优化后: 锁外做耗时工作
LIST_HEAD(unmap_list);
/* 快速收集需要unmap的项 */
spin_lock(&dev->lock);
list_for_each_entry_safe(mapping, next, &dev->mappings, list) {
if (overlaps(mapping, range))
list_move(&mapping->list, &unmap_list);
}
spin_unlock(&dev->lock);
/* 锁外处理 */
list_for_each_entry_safe(mapping, next, &unmap_list, list)
device_unmap(dev, mapping);
策略2: 使用trylock避免死锁
static bool device_invalidate(struct mmu_interval_notifier *mni, ...)
{
struct device_ctx *ctx = container_of(mni, ...);
/* 关键: 不能阻塞等待(可能死锁) */
if (!mutex_trylock(&ctx->device_lock))
return false; // 返回false让内核重试整个操作
mmu_interval_set_seq(mni, cur_seq);
device_flush_tlb(ctx);
mutex_unlock(&ctx->device_lock);
return true;
}
11.3.3 优化interval粒度
问题: 粒度选择影响性能
// 方案A: 细粒度(每页一个notifier)
for (i = 0; i < 262144; i++) // 1GB / 4KB
mmu_interval_notifier_insert(¬ifiers[i], mm,
addr + i * PAGE_SIZE, PAGE_SIZE, &ops);
/* 优点: invalidate精确,只flush相关页
* 缺点:
* – 26万个interval tree节点
* – 查找开销 O(log N) 变大
* – 内存开销: 26万 * 64字节 = 16MB
*/
// 方案B: 粗粒度(整个区域一个notifier)
mmu_interval_notifier_insert(¬ifier, mm, addr, 1GB, &ops);
/* 优点: 管理简单,内存开销小
* 缺点:
* – munmap(4KB) 也会触发flush整个1GB的设备TLB
* – 假共享严重
*/
// 方案C: 中等粒度(2MB块)★ 推荐
for (i = 0; i < 512; i++) // 1GB / 2MB
mmu_interval_notifier_insert(¬ifiers[i], mm,
addr + i * (2UL << 20), 2UL << 20, &ops);
/* 平衡点:
* – 512个节点,查找快
* – 内存开销: 32KB (可接受)
* – invalidate粒度: 2MB (与THP对齐)
* – 适合大多数场景
*/
调优指南:
工作负载特征 推荐粒度 原因
───────────────────────────────────────────────────
稀疏访问(<10%利用率) 4KB-64KB 减少假共享
密集访问(>90%利用率) 2MB-1GB 减少管理开销
随机访问 匹配访问模式 自适应调整
大页应用(THP/HugeTLB)2MB/1GB 与页大小对齐
11.4 实战案例
案例: GPU渲染卡顿问题
问题描述:
应用: Blender 3D渲染 (GPU加速)
现象: 渲染帧率从60fps突然降至5fps
环境: 32GB内存,使用了28GB(内存压力大)
步骤1: 诊断
# 查看GPU驱动日志
$ dmesg | grep amdgpu
[drm:amdgpu_hmm_range_get_pages] *ERROR* hmm_range_fault timeout after 300 retries
# 启用tracepoint
$ echo 1 > /sys/kernel/debug/tracing/events/mmu_notifier/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
<...>-1000 [002] hmm_range_fault: start=0x7f... end=0x7f... ret=-EBUSY
<...>-1000 [002] hmm_range_fault: start=0x7f... end=0x7f... ret=-EBUSY
<...>-1000 [002] hmm_range_fault: start=0x7f... end=0x7f... ret=-EBUSY
# … 持续重试
# 根因: 内存压力 -> kswapd频繁回收 -> 大量invalidate -> 重试风暴
步骤2: 优化
// 原代码(amdgpu_hmm.c)
hmm_range->start = start;
hmm_range->end = start + npages * PAGE_SIZE; // 最大512MB
ret = hmm_range_fault(hmm_range); // 遍历50ms -> 易冲突
// 修改为分段遍历
#define MAX_WALK_SIZE (2UL << 20) // 2MB
do {
hmm_range->end = min(hmm_range->start + MAX_WALK_SIZE, end);
timeout = jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
retry:
hmm_range->notifier_seq = mmu_interval_read_begin(notifier);
ret = hmm_range_fault(hmm_range);
if (ret == –EBUSY) {
if (time_after(jiffies, timeout))
break;
goto retry;
}
hmm_range->hmm_pfns += (hmm_range->end – hmm_range->start) >> PAGE_SHIFT;
hmm_range->start = hmm_range->end;
} while (hmm_range->start < end);
步骤3: 验证
# 优化前
$ cat /sys/kernel/debug/dri/0/amdgpu_hmm_stats
invalidate_count: 10523
retry_count: 8942
retry_rate: 85% # <- 严重
timeout_count: 142
avg_latency: 523us
# 优化后
$ cat /sys/kernel/debug/dri/0/amdgpu_hmm_stats
invalidate_count: 10891
retry_count: 612
retry_rate: 5.6% # <- 正常
timeout_count: 0
avg_latency: 87us
# 应用表现
Blender帧率: 5fps –> 58fps (11.6x提升)
11.5 性能调优清单
设计阶段
□ 选择mmu_interval_notifier而非mmu_notifier
□ 选择合适的interval粒度(推荐2MB)
□ 设计非阻塞的invalidate回调(使用trylock)
□ 规划重试策略和超时机制
实现阶段
□ 大范围操作拆分为小块(推荐2-4MB)
□ 在长操作中周期性check_retry
□ 缩小spinlock临界区
□ 避免在invalidate回调中做耗时操作
□ 实现设备侧异步TLB flush
测试阶段
□ 添加性能统计(retry_rate, timeout_rate, avg_latency)
□ 使用ftrace验证行为
□ 压力测试(内存压力 + 高负载)
□ 使用lockdep检测死锁
□ 监控长尾延迟(P99, P999)
部署阶段
□ 导出debugfs统计接口
□ 设置告警阈值(retry_rate > 15%)
□ 监控生产环境指标
□ 准备降级方案(如禁用HMM)
11.6 小结
核心要点:
三大性能问题:
- 重试风暴(最常见)-> 分段遍历
- 锁竞争(高并发)-> 缩小临界区
- 设备开销(硬件)-> 异步flush
诊断方法:
- 先看统计指标(retry_rate, timeout_rate)
- 再用tracepoint定位热点
- 最后用perf分析细节
优化原则:
- 减少冲突窗口(小粒度遍历)
- 快速失败(提前check)
- 避免阻塞(trylock)
性能目标:
- retry_rate < 5%
- timeout_rate < 0.1%
- avg_latency < 100us
关键经验:
90%的性能问题来自重试风暴,优先优化遍历粒度。 使用tracepoint比盲目优化更有效。
🔗 导航
- 上一章: 第10章: MMU notifier与HMM的协作
- 下一章: 第12章: MMU notifier内核演进与未来方向
- 返回目录: Linux MMU Notifier 机制与应用系列目录
网硕互联帮助中心





评论前必须登录!
注册