前面我们已经聊过四大诊断类型中的前三个Sampling,Tracine,Line-by-Line,这篇补上最后一个诊断类型Timeline,这也是真实场景中使用最多的,它能够采集到所有它能采集到的,比如:摘要:using System;using System.Diagnostics;namespaceMatrixOperations{internalclassProgram{static voidMain(string args){constint baseSiz
线程栈数据 (函数执行时间)
ETW事件 (Windows日志)
TPL数据(方便绘制异步栈)
按时序绘制时间轴 (Timeline)
1. 一个简单的测试案例为了方便演示,我们还是用上一篇的矩阵运算的例子,参考代码如下:
using System;
using System.Diagnostics;
namespaceMatrixOperations
{
internalclassProgram
{
static voidMain(string args)
{
constint baseSize =1000;
constint iterations =3;
for (int i =0; i < iterations; i++)
{
int matrixSize = baseSize - (i *100);
PerformMatrixMultiplication(matrixSize);
}
}
static voidPerformMatrixMultiplication(int matrixSize)
{
Console.WriteLine($"\n=== 处理 {matrixSize}x{matrixSize} 矩阵 ===");
Console.WriteLine("创建随机矩阵...");
var matrixA = GenerateRandomMatrix(matrixSize, matrixSize);
var matrixB = GenerateRandomMatrix(matrixSize, matrixSize);
Console.WriteLine("执行矩阵乘法...");
var timer = Stopwatch.StartNew;
var resultMatrix = MultiplyMatrices(matrixA, matrixB);
timer.Stop;
Console.WriteLine($"运算完成,耗时: {timer.Elapsed.TotalSeconds:0.000} 秒");
DisplayMatrixPreview(resultMatrix);
}
staticdouble GenerateRandomMatrix(int rows, int cols)
{
var random = new Random;
var matrix = newdouble[rows, cols];
for (int i =0; i < rows; i++)
{
for (int j =0; j < cols; j++)
{
matrix[i, j] = random.NextDouble *100;
}
}
return matrix;
}
staticdouble MultiplyMatrices(double matrixA, double matrixB)
{
int aRows = matrixA.GetLength(0);
int aCols = matrixA.GetLength(1);
int bCols = matrixB.GetLength(1);
if (matrixA.GetLength(1) != matrixB.GetLength(0))
thrownew ArgumentException("矩阵维度不匹配");
var result = newdouble[aRows, bCols];
for (int i =0; i < aRows; i++)
{
for (int j =0; j < bCols; j++)
{
double sum =0;
for (int k =0; k < aCols; k++)
{
sum += matrixA[i, k] * matrixB[k, j];
}
result[i, j] = sum;
}
}
return result;
}
static voidDisplayMatrixPreview(double matrix, int previewSize =3)
{
Console.WriteLine($"\n矩阵预览 (前{previewSize}x{previewSize}个元素):");
int rows = Math.Min(previewSize, matrix.GetLength(0));
int cols = Math.Min(previewSize, matrix.GetLength(1));
for (int i =0; i < rows; i++)
{
for (int j =0; j < cols; j++)
{
Console.Write($"{matrix[i, j],8:0.00} ");
}
Console.WriteLine;
}
}
}
}
接下来打开 dotrace,选择Timeline模式,采样频次默认是1000samples/s,即每秒1000次采样,这个相比Sampling模式的5~11s 要快得多,也让采集结果成倍的增加,如果你想采集的更密集些,可以设置为8000 samples/sec,最后就是启动 Start,截图如下:
在卦中的时间轴上标记着二类数据:
线程活动的时序分布。
GC Wait时间(GC触发时的累计阻塞时间)
结果都有了,接下来回答三个问题来熟悉下Timeline模式吧。1. 哪个函数最耗时宏观观察时间轴,我们发现 Main线程的轴上有一段很长的深绿色,说明它曾在这个时段活动,接下来在Thread State面板中选择Running状态,然后选择Main线程进行过滤。至此我们找到了耗费cpu的热点函数。
2. 为什么深绿色是不间断的这是一个挺有意思的问题,熟悉操作系统知识的朋友应该知道Windows是抢占式操作系统,每个线程都会分配到一个时间片,在落地上用量程(Quantum)表示,所以这些间断的很显然是Main线程从失宠到得宠的一个过程,即图中的staticdouble MultiplyMatrices(double matrixA, double matrixB)
{
int aRows = matrixA.GetLength(0);
int aCols = matrixA.GetLength(1);
int bCols = matrixB.GetLength(1);
if (matrixA.GetLength(1) != matrixB.GetLength(0))
thrownew ArgumentException("矩阵维度不匹配");
var result = newdouble[aRows, bCols];
for (int i =0; i < aRows; i++)
{
for (int j =0; j < bCols; j++)
{
double sum =0;
for (int k =0; k < aCols; k++)
{
sum += matrixA[i, k] * matrixB[k, j];
}
result[i, j] = sum;
}
}
GC.Collect;//故意触发阻塞GC
return result;
}
启动 dottrace 跟踪,完成之后截图如下:
打开卦之后,选中 Main线程,GarbageCollection事件以及Running状态,可以清晰的看到,当前触发了 3次 阻塞GC,1次后台GC。
整体上来说,dottrace最大的优点就是时间轴,在某些场景下比 perfview 的表格展示法更加清楚,timeline模式也是在真实场景中用的最多的一种洞察方式。作为JetBrains社区内容合作者,如有购买jetbrains的产品,可以用我的折扣码 HUANGXINCHENG,有25%的内部优惠哦。来源:opendotnet