c# 如何用 dotnet-trace 分析并发性能问题

7次阅读

不能。do.net-trace 仅采集 ContentionStart 等底层事件,需配合 convert 导出 speedscope 并分析时间轴与才能定位锁瓶颈;必须显式启用 0x200000000000 Contention 事件标志,且仅支持 .NET 6+。

c# 如何用 dotnet-trace 分析并发性能问题

dotnet-trace 能否直接定位并发瓶颈?

不能。dotnet-trace 本身不分析线程竞争或锁等待,它只采集底层运行时事件(如 ThreadStartThreadPoolWorkerThreadStartMonitorEnterContentionStart)。真正识别“哪个锁卡住了多少线程”,得靠后续用 dotnet-trace convert 导出为 nettracespeedscope,再结合时间轴和堆下钻——关键在采集时是否启用了足够粒度的事件。

必须开启的 trace provider 和参数

默认 dotnet-trace collect 不捕获锁争用事件。漏掉 microsoft-windows-DotNETRuntime:0x200000000000(即 Contention 位),就看不到任何 ContentionStart/ContentionStop 事件,等于白采。

  • --providers 显式指定运行时事件:
    dotnet-trace collect --process-id 12345 --providers "Microsoft-windows-DotNETRuntime:0x200000000000,0x80,0x80:0x8000000000000000"
  • 其中 0x200000000000ContentionEvent flag,0x80 是 level(Warning),0x8000000000000000 是 keywords for ThreadPool(可选但推荐)
  • 若要同时看 GC 和 JIT 影响,补上 Microsoft-Windows-DotNETRuntime:0x400000000000,0x4(GC)和 0x100000000000(JIT)
  • 避免用 --profile 快捷模式,它默认不包含 Contention 事件

如何从 nettrace 中识别真实并发阻塞点

导出后用 dotnet-trace convert -f speedscope trace.nettrace,在 Speedscope ui 中切到 Flame GraphTop Down 视图,重点找:

  • 长条状、横向铺开的 ContentionStart 事件(不是函数调用,是 runtime 发出的独立事件)
  • 其堆栈中紧邻的上层方法——通常是 Monitor.Enterlock(...) 对应的 IL 或源码行
  • 多个线程在相同 Monitor 对象(看堆栈里 Object.GetHashCode() 或字段名)上反复出现 ContentionStartContentionStop 成对出现,说明存在热点
  • 注意区分假阳性:短于 1ms 的争用通常无实际影响;而 >10ms 且高频出现的,大概率是瓶颈

常见误操作与兼容性陷阱

Windows 上用 dotnet-trace 分析 .NET 6+ 进程基本稳定,但以下几点极易踩坑:

  • .NET 5 及更早版本不支持 Contention 事件 —— 升级 runtime 至少到 .NET 6.0
  • linux 上需确保内核支持 perf_event,且进程有 perf_event_paranoid 权限(常需 sudo sysctl -w kernel.perf_event_paranoid=1
  • --duration 限制采集时间(如 --duration 30s),否则高并发场景下 trace 文件可能达 GB 级,转换失败
  • 不要在生产环境长期启用 Contention 事件:它会带来 ~5–10% CPU 开销,仅用于问题复现阶段

真正难的不是采集,而是把 ContentionStart 时间戳、线程 ID、锁对象哈希、调用堆栈四者对齐。一次 trace 往往要反复导出、过滤、比对三次以上才能确认根因。

text=ZqhQzanResources