Stopwatch.GetTimestamp() 返回的是底层硬件计时器的滴答数(ticks),单位为未公开起始点以来的整数计数值,需配合 Stopwatch.Frequency 换算为秒或纳秒,不可直接当作时间单位使用。

Stopwatch.GetTimestamp() 返回的是什么单位?
Stopwatch.GetTimestamp() 返回的是一个 long 类型的整数,代表自某个未公开起始点以来的「计时器滴答数(ticks)」,不是毫秒、纳秒,也不是 .NET 的 DateTime 那种 100 纳秒单位。它的实际物理单位取决于底层硬件计时器(通常是 CPU 的高精度性能计数器,如 TSC),所以**不能直接当时间用**,必须配合 Stopwatch.Frequency 换算。
常见错误是把它当成纳秒或直接减完就除 1000 当毫秒——这在大多数现代 Windows 机器上会严重偏大(因为 TSC 频率常是 2–4 GHz,即每秒 20–40 亿次滴答)。
-
Stopwatch.Frequency是每秒多少次GetTimestamp()滴答,例如3_200_000_000 - 两次调用差值
delta除以Frequency才是秒:(double)delta / Stopwatch.Frequency - 想转纳秒?乘
1_000_000_000:(delta * 1_000_000_000) / Stopwatch.Frequency(注意整数溢出风险)
为什么不用 Stopwatch.ElapsedMilliseconds 测微秒级开销?
Stopwatch.ElapsedMilliseconds 只返回 long,精度固定为毫秒,会直接截断小数部分;而 Stopwatch.Elapsed.TotalMilliseconds 虽是 double,但内部仍基于相同滴答计数,只是做了浮点换算——问题不在它“不准”,而在**默认构造和测量方式容易掩盖抖动**。
真正影响微秒级测量可靠性的,是测量本身引入的噪声:JIT 预热不足、GC 干扰、线程调度、CPU 频率缩放(Intel SpeedStep / AMD Cool'n'Quiet)、甚至测量代码自身执行时间。比如测一个空方法,GetTimestamp() 调用本身的开销可能就占几十纳秒。
- 务必用
Stopwatch.IsHighResolution确认底层用了 HPET 或 TSC,否则 fallback 到QueryPerformanceCounter甚至GetTickCount64,精度可能只有 10–15ms - 单次测量毫无意义;至少跑几百到几万次,取中位数或 p95,再减去空循环基线(control baseline)
- 用
Thread.Sleep(0)或GC.Collect()强制干扰后重测,观察方差是否突增——这是判断测量是否被系统干扰的快速信号
如何安全地用 GetTimestamp() 做低开销高频采样?
如果你在写高性能网络库、实时音频处理或游戏逻辑帧统计,需要每帧/每次回调都打点,Stopwatch.Start()/Stop() 的方法调用开销(尤其是检查状态、读寄存器)可能成为瓶颈。GetTimestamp() 是静态、无状态、无锁的,适合内联采样。
具备更多的新特性: A.具有集成度更高的平台特点,集中体现了信息、文档在办公活动中交流的开放性与即时性的重要。 B.提供给管理员的管理工具,使系统更易于管理和维护。 C.产品本身精干的体系结构再加之结合了插件的设计思想,使得产品为用户度身定制新模块变得非常快捷。 D.支持对后续版本的平滑升级。 E.最价的流程管理功能。 F.最佳的网络安全性及个性化
private static readonly long _frequency = Stopwatch.Frequency; private long _startTick;public void BeginSample() => _startTick = Stopwatch.GetTimestamp();
public double EndSampleInMs() { long end = Stopwatch.GetTimestamp(); long delta = end - _startTick; return (double)delta * 1000.0 / _frequency; // 毫秒,保留小数 }
注意:不要在多线程共享同一个 _startTick 字段;也不要跨线程复用同一实例做 Begin/End —— GetTimestamp() 是线程安全的,但你的字段不是。
- 避免在
try/finally中调用EndSampleInMs(),异常路径可能让_startTick滞留成脏值 - 如果需长期运行的持续采样(如每毫秒打一次点),考虑用环形缓冲区存原始
long时间戳,最后统一换算,减少浮点运算次数 - Windows 上若启用了 “Timer resolution” 工具(如 PowerCfg /energy or LatencyMon),可能强制系统使用更低分辨率计时器,
IsHighResolution会返回false,此时GetTimestamp()实际退化为低精度 API
Stopwatch.GetTimestamp() 在 .NET 6+ 跨平台行为差异
.NET 5 开始统一了各平台计时器抽象,但底层实现仍有区别:Linux 默认用 clock_gettime(CLOCK_MONOTONIC),macOS 用 mach_absolute_time(),而 Windows 优先用 RDTSC(带序列化)或 QueryPerformanceCounter。这意味着即使 Frequency 相同,多次调用的抖动特征也不同。
最易被忽略的一点:**某些虚拟机(特别是旧版 Hyper-V 或 VMWare Workstation)会禁用 TSC 或模拟低频计数器,导致 Frequency 只有 10 MHz 甚至更低,且 GetTimestamp() 调用延迟剧烈波动**。这不是 bug,是虚拟化层有意为之的安全/兼容性策略。
- 上线前务必在目标环境运行
Stopwatch.IsHighResolution && Stopwatch.Frequency > 1_000_000校验 - 容器环境(如 Docker on WSL2)同样受宿主机虚拟化影响,
GetTimestamp()在 WSL2 中实际走的是 Windows 主机的 QPC,但存在额外上下文切换开销 - .NET 7+ 新增
System.Diagnostics.Metrics,对高频指标采集更友好,但底层仍依赖Stopwatch—— 所以理解GetTimestamp()的本质,比换新 API 更关键










