云计算百科
云计算领域专业知识百科平台

第11章: 性能分析与调优

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(&notifiers[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(&notifier, mm, addr, 1GB, &ops);

/* 优点: 管理简单,内存开销小
* 缺点:
* – munmap(4KB) 也会触发flush整个1GB的设备TLB
* – 假共享严重
*/

// 方案C: 中等粒度(2MB块)★ 推荐
for (i = 0; i < 512; i++) // 1GB / 2MB
mmu_interval_notifier_insert(&notifiers[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 机制与应用系列目录
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 第11章: 性能分析与调优
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!