一:背景

1. 讲故事

在用 dotTrace 对程序进行性能评测的时候,有一个非常重要的概念需要使用者明白,那就是 时间度量 (Time measurement),主要分为两种。

  • 墙钟时间
  • 线程时间

在 dotTrace 中有四种测量维度,其中 Real time 对应着 墙钟时间,截图如下:

二:时间度量分析

1. 墙钟时间

墙钟时间 顾名思义就是墙上的时钟,只要给它上电它就能跑,无视这人世间发生的任何变化,屹立千年而不倒。

用程序的话术就是它可以追踪到线程的 上班时间下班时间,在 dottrace 中用 performance countercpu instruction 两种方式,本质上来说前者是后者的包装。

如果有朋友比较懵的话,可以用 C 调一下对应的 Win32 api 就明白了,参考代码如下:


#include <windows.h>
#include <stdio.h>
#include <intrin.h> // 1. Real Time (Performance Counter)
void measure_real_time_perf_counter() { LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&start);
// 模拟工作负载(Sleep 不占用 CPU 时间)
Sleep(100);
QueryPerformanceCounter(&end);
double elapsed = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart;
printf("RealTime (Performance Counter): %.2f ms\n", elapsed * 1000);
} // 2. Real Time (CPU TSC)
void measure_cpu_tsc() {
unsigned __int64 start = __rdtsc();
// 模拟工作负载(注意:Sleep 不会增加 TSC)
Sleep(100); // 此处仅为演示,实际应用应使用 CPU 密集型操作
unsigned __int64 end = __rdtsc();
printf("RealTime (CPU TSC): %llu cycles\n", end - start);
} int main() {
printf("----- Measuring Time with Win32 APIs -----\n");
measure_real_time_perf_counter();
measure_cpu_tsc();
return 0;
}

从卦中可以看到这两种模式都可以追踪到线程的 下班时间,接下来稍微解释下。

  1. QueryPerformanceCounter,QueryPerformanceFrequency

上面的 QueryPerformanceCounter 是Windows硬件定时器的计数,而 QueryPerformanceFrequency 是这个定时器的时钟频率 (速度),所以时间值可以用小学的公式 T= S/V 算出来。

  1. __rdtsc

tsc 是cpucore中的一个硬件寄存器,这个寄存器记录了cpu上电之后的时钟周期数,要想算出时间需要知道cpu的时钟频率,截图如下:

有些朋友就说了,你拿 差值/2.2GHZ 不就是时间吗?这么算的话其实不准的,因为 2.2GHZ 是基准频率,CPU在工作时会有上下浮动,比如写到这里的时候,当前的CPU的频率是4.93Ghz,这个就差的有点大了,截图如下:

所以比较合理的做法需要校准下 时钟频率,我觉得 dottrace 内部应该是这么做的,毕竟这东西是闭源的,为了最简化,这里就用一个硬编码,参考如下:


// 2. Real Time (CPU TSC)
void measure_cpu_tsc() { double CPU_FREQUENCY_GHZ = 2.2; // 例如 2.2 GHz unsigned __int64 start = __rdtsc(); Sleep(100); // 实际应用应替换为 CPU 密集型操作
unsigned __int64 end = __rdtsc(); // 计算时间(毫秒):
// 1. 计算时钟周期差值
unsigned __int64 cycles = end - start;
// 2. 转换为秒:cycles / (frequency in Hz)
double seconds = (double)cycles / (CPU_FREQUENCY_GHZ * 1e9);
// 3. 转换为毫秒
double milliseconds = seconds * 1000.0; printf("RealTime (CPU TSC): %.2f ms\n", milliseconds);
}

2. 线程时间

线程时间 它追踪的是 线程活动的时间,即线程的上班时间,同样 dottrace 也支持两种,分别为 Thread cycle timeThread time ,本质上来说也是直接调用 Win32 Api 算出来的,参考代码如下:



#include <windows.h>
#include <stdio.h>
#include <intrin.h> // 1. Thread Time (User + Kernel Mode CPU Time)
void measure_thread_time() {
FILETIME creation, exit, kernel_start, user_start, kernel_end, user_end;
HANDLE thread = GetCurrentThread();
GetThreadTimes(thread, &creation, &exit, &kernel_start, &user_start);
// 模拟 CPU 密集型工作
volatile int sum = 0;
for (int i = 0; i < 500000000; i++) sum += i;
GetThreadTimes(thread, &creation, &exit, &kernel_end, &user_end); // 将 FILETIME(100ns 单位)转换为微秒
ULARGE_INTEGER kernel_time, user_time;
kernel_time.LowPart = kernel_end.dwLowDateTime - kernel_start.dwLowDateTime;
kernel_time.HighPart = kernel_end.dwHighDateTime - kernel_start.dwHighDateTime;
user_time.LowPart = user_end.dwLowDateTime - user_start.dwLowDateTime;
user_time.HighPart = user_end.dwHighDateTime - user_start.dwHighDateTime; printf("Thread Time: Kernel=%.2f ms, User=%.2f ms\n",
kernel_time.QuadPart / 10000.0,
user_time.QuadPart / 10000.0);
} // 2. Thread Cycle Time
void measure_thread_cycle_time() {
ULONG64 start, end;
HANDLE thread = GetCurrentThread();
QueryThreadCycleTime(thread, &start);
// 模拟 CPU 密集型工作
volatile int sum = 0;
for (int i = 0; i < 500000000; i++) sum += i;
QueryThreadCycleTime(thread, &end);
printf("Thread Cycle Time: %llu cycles\n", end - start);
} int main() {
printf("----- Measuring Time with Win32 APIs -----\n");
measure_thread_time();
measure_thread_cycle_time();
return 0;
}

同样的也来稍微解读下。

  1. GetThreadTimes

这个方法是直接获取线程内核数据结构 KThread 里的 KernelTime 和 UserTime 字段,我可以用 windbg 给大家演示下。


lkd> !process 0 2 notepad.exe
PROCESS ffffb98d5cc1a080
SessionId: none Cid: 6af4 Peb: ed94d6c000 ParentCid: 22a0
DirBase: 3f045a000 ObjectTable: ffffe1039bf39040 HandleCount: 848.
Image: Notepad.exe THREAD ffffb98d5f0a3080 Cid 6af4.60cc Teb: 000000ed94d6d000 Win32Thread: ffffb98d69a53e20 WAIT: (WrUserRequest) UserMode Non-Alertable
ffffb98d6d64a9c0 QueueObject THREAD ffffb98d8766c080 Cid 6af4.3894 Teb: 000000ed94d6f000 Win32Thread: 0000000000000000 WAIT: (Unknown) UserMode Alertable
ffffb98d78286140 QueueObject
... lkd> dt nt!_KTHREAD ffffb98d5f0a3080 -y KernelTime UserTime
+0x28c KernelTime : 4
+0x2dc UserTime : 2

要注意的是上面的字段是 100纳秒 为单位的,即 100纳秒=0.1ms,所以上面分别为 0.4ms0.2ms

  1. QueryThreadCycleTime

在 _KTHREAD 中有一个字段 CycleTime,当线程上班线程下班时都要打卡到 CycleTime 字段里,同样也可以用windbg 验证。


lkd> dt nt!_KTHREAD ffffb98d5f0a3080 -y CycleTime
+0x048 CycleTime : 0x6db6621 lkd> ? 0x6db6621
Evaluate expression: 115041825 = 00000000`06db6621

接下来就是如何将时钟周期差值转为 ms 呢?这需要知道CPU当时的时钟频率,同样我也简化一下,参考代码如下:


// 2. Thread Cycle Time
void measure_thread_cycle_time() {
ULONG64 start, end;
HANDLE thread = GetCurrentThread();
QueryThreadCycleTime(thread, &start); volatile int sum = 0;
for (int i = 0; i < 500000000; i++) sum += i; QueryThreadCycleTime(thread, &end); const double CPU_FREQUENCY_GHZ = 2.2; // 假设 CPU 主频 2.2 GHz
double milliseconds = (double)(end - start) / (CPU_FREQUENCY_GHZ * 1e6); // 直接计算毫秒 printf("Thread Cycle Time: %.2f ms\n", milliseconds);
}

哈哈,这两个值偏差还是有点大哈。

三:总结

理解墙钟时间和线程时间的底层原理,对我们后续的场景分析特别有用,比如前者适合分析为什么程序卡死?后者适合分析是哪些线程吃了那么多的CPU?

作为JetBrains社区内容合作者,如有购买jetbrains的产品,可以用我的折扣码 HUANGXINCHENG,有25%的内部优惠哦。

DotTrace系列:3. 时间度量之墙钟时间和线程时间的更多相关文章

  1. 【转载】Java垃圾回收内存清理相关(虚拟机书第三章),GC日志的理解,CPU时间、墙钟时间的介绍

    主要看<深入理解Java虚拟机> 第三张 P84 开始是垃圾收集相关. 1. 1960年诞生于MIT的Lisp是第一门采用垃圾回收的语言. 2. 程序计数器.虚拟机栈.本地方法栈3个区域随 ...

  2. 【HANA系列】SAP HANA查看某一用户最后登录时间及无效连接次数

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA查看某一用户最后 ...

  3. JVM故障分析系列之四:jstack生成的Thread Dump日志线程状态

    JVM故障分析系列之四:jstack生成的Thread Dump日志线程状态  2017年10月25日  Jet Ma  JavaPlatform JVM故障分析系列系列文章 JVM故障分析系列之一: ...

  4. mysql取出现在的时间戳和时间时间戳转成人类看得懂的时间

    mysql取出现在的时间戳和时间时间戳转成人类看得懂的时间,我们在mysql里面他封装了一个内置的时间戳转化的函数,比如我们现在的时间戳是:1458536709 ,"%Y-%m-%d&quo ...

  5. java获取本月开始时间和结束时间、上个月第一天和最后一天的时间以及当前日期往前推一周、一个月

    import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.uti ...

  6. [Sciter系列] MFC下的Sciter–5.Sciter中GUI线程研究

    [Sciter系列] MFC下的Sciter–5.Sciter中GUI线程研究,目前MFC存在问题,win32没问题. 本系列文章的目的就是一步步构建出一个功能可用,接口基本完善的基于MFC框架的Sc ...

  7. laydate时间组件在火狐浏览器下有多时间输入框时只能给第一个输入框赋值的问题

    遇到的问题: laydate时间组件在火狐浏览器下有多时间输入框时只能给第一个输入框赋值的问题(safari下也有同样问题); 解决办法: 给laydate绑定id; 解决前代码: <input ...

  8. timeout Timeout时间已到.在操作完成之前超时时间已过或服务器未响应

    Timeout时间已到.在操作完成之前超时时间已过或服务器未响应 问题 在使用asp.net开发的应用程序查询数据的时候,遇到页面请求时间过长且返回"Timeout时间已到.在操作完成之间超 ...

  9. Java内存泄漏分析系列之四:jstack生成的Thread Dump日志线程状态

    原文地址:http://www.javatang.com Thread Dump日志的线程信息 以下面的日志为例: "resin-22129" daemon prio=10 tid ...

  10. (摘)timeout Timeout时间已到.在操作完成之前超时时间已过或服务器未响应的几种情况

    Timeout时间已到.在操作完成之前超时时间已过或服务器未响应 问题 在使用asp.net开发的应用程序查询数据的时候,遇到页面请求时间过长且返回"Timeout时间已到.在操作完成之间超 ...

随机推荐

  1. Vite CVE-2025-30208 安全漏洞

    Vite CVE-2025-30208 安全漏洞 一.漏洞概述 CVE-2025-30208 是 Vite(一个前端开发工具提供商)在特定版本中存在的安全漏洞.此漏洞允许攻击者通过特殊的 URL 参数 ...

  2. SpringAI用嵌入模型操作向量数据库!

    嵌入模型(Embedding Model)和向量数据库(Vector Database/Vector Store)是一对亲密无间的合作伙伴,也是 AI 技术栈中紧密关联的两大核心组件,两者的协同作用构 ...

  3. Web前端入门第 27 问:你知道 CSS 被浏览器分为了几大类吗?

    埋头苦写多年的 CSS,从没注意到 CSS 被浏览器分了类,直到偶然的一次翻阅开发者工具,才发现原来 CSS 属性也被浏览器归类收纳了. Chrome 下面是 Chrome 的开发者工具中 CSS 的 ...

  4. LeetCode1464. 数组中两元素的最大乘积-JAVA

    题目 给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值.请你计算并返回该式的最大值. 示例 1: 输入:nums = ...

  5. MySQL 中如何进行 SQL 调优?

    MySQL 中如何进行 SQL 调优? SQL 调优是提高数据库查询性能的过程,主要目的是减少查询的响应时间和系统的负载.下面是一些常见的 SQL 调优方法和技巧. 1. 使用索引 索引的使用可以显著 ...

  6. wordpress插件开发时如何通过js调用图库/媒体选择器的问题

    效果: 原文地址: wordpress插件开发通过js调用图库/媒体选择器的问题 - 搜栈网 (seekstack.cn)

  7. CF1546B题解

    看了题面,一道简单的假交互题 题目传送门,另一个传送门 读好题目很重要 AquaMoon 有 nnn 个长度为 mmm 的字符串,其中 nnn 是奇数. 然后她选取 n−1n-1n−1 个字符串,将它 ...

  8. K8s Pod 控制器介绍及应用示例

    Kubernetes 官方文档:Pod 控制器 Pod控制器介绍 Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类: 自主式pod:kuber ...

  9. Python3循环结构(二) while循环

    Python3 while循环 当循环次数无界时通常会使用while循环. 1.使用while循环输出九九乘法表 i=1 while i < 10: j = 1 while j < i + ...

  10. Sentinel——网关限流

    目录 网关限流 route维度 自定义异常 重定向 自定义结果 API维度 网关限流代码配置 网关限流 Sentinel 支持对 Spring Cloud Gateway.Zuul 等主流的 API ...