王永庆 發表於 2025-6-20 13:57:00

DotTrace系列:1. 理解四大经典的诊断类型(上)

<h2 id="一背景">一:背景</h2>
<h3 id="1-讲故事">1. 讲故事</h3>
<p>在所有与 .NET相关的JetBrains产品中,我觉得 <code>DotTrace</code> 是最值得深入学习和研究的一款,个人觉得它的优点如下:</p>
<ol>
<li>跨平台诊断 (Windows,Linux,MacOS)</li>
<li>兼容 dotnet-trace 产出的 nettrace。</li>
<li>优秀的可视化界面,尤其是 timeline 时间轴。</li>
<li>支持自我托管和代码的局部诊断。</li>
</ol>
<p>在我的 <code>.NET高级调试知识系列</code>下,这是一款不可或缺的利器,话不多说,我们就从<code>四大诊断类型</code>来开聊吧。</p>
<h2 id="二四大诊断类型">二:四大诊断类型</h2>
<h3 id="1-sampling-模式">1. Sampling 模式</h3>
<p>如果你的程序出现了性能变慢,但你又不知道是哪里的变慢?不知道从何入手,这时候就可以使用 <code>Sampling</code> 模式,它是从<code>应用程序</code>的角度帮你宏观洞察程序的性能,相当于性能洞察的第一道关卡。</p>
<p>Sampling 模式默认 5~11ms 对各个线程栈进行采样,通过大量的样本就能通过 <code>group by</code> 的方式计算出每个函数的累计执行时间,这里有一个小细节,如果 <code>函数执行时间&lt;5ms</code> 的话,肯定是捕获不到的,这个也能理解。</p>
<p>接下来我们写一个简单的矩阵运算,然后寻找耗时的函数,参考代码如下:</p>
<pre><code class="language-C#">
using System;
using System.Diagnostics;

namespace MatrixOperations
{
    internal class Program
    {
      static void Main(string[] args)
      {
            const int baseSize = 1000;
            const int iterations = 3;

            for (int i = 0; i &lt; iterations; i++)
            {
                int matrixSize = baseSize - (i * 100);
                PerformMatrixMultiplication(matrixSize);
            }
      }

      static void PerformMatrixMultiplication(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);
      }

      static double[,] GenerateRandomMatrix(int rows, int cols)
      {
            var random = new Random();
            var matrix = new double;

            for (int i = 0; i &lt; rows; i++)
            {
                for (int j = 0; j &lt; cols; j++)
                {
                  matrix = random.NextDouble() * 100;
                }
            }

            return matrix;
      }

      static double[,] 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))
                throw new ArgumentException("矩阵维度不匹配");

            var result = new double;

            for (int i = 0; i &lt; aRows; i++)
            {
                for (int j = 0; j &lt; bCols; j++)
                {
                  double sum = 0;
                  for (int k = 0; k &lt; aCols; k++)
                  {
                        sum += matrixA * matrixB;
                  }
                  result = sum;
                }
            }

            return result;
      }

      static void DisplayMatrixPreview(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 &lt; rows; i++)
            {
                for (int j = 0; j &lt; cols; j++)
                {
                  Console.Write($"{matrix,8:0.00} ");
                }
                Console.WriteLine();
            }
      }
    }
}

</code></pre>
<p>打开 dottrace,选择 <code>Sampling</code> 模式,点击 Start 即可,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202506/214741-20250620135533117-446480795.png" alt="" loading="lazy"></p>
<p>程序在运行完之后,会自动退出,dottrace 会自动打开收集到的追踪文件,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202506/214741-20250620135533121-1770526303.png" alt="" loading="lazy"></p>
<p>从卦中可以挖出如下信息:</p>
<ol>
<li>dottrace采样了 21s。</li>
<li>非托管层占 14s + 托管层占 7s</li>
<li>MultiplyMatrices 吃了7s,相对其他方法来说最耗cpu</li>
</ol>
<p>要想知道 MultiplyMatrices 为什么会吃 7s,这个就属于细节问题了,Sampling模式就无法告诉你答案了。</p>
<blockquote>
<p>值得一提的是,Sampling 属于大粒度的性能跟踪,生成的采样文件很小,适合天级别的长期监控。</p>
</blockquote>
<h3 id="2-tracing-模式">2. Tracing 模式</h3>
<p>刚才我们从<code>应用程序</code> 角度做了一个宏观洞察,发现了可疑函数 <code>MultiplyMatrices</code> ,我相信你此时会非常感兴趣,这个方法为什么会吃那么多的时间???</p>
<p>这就是本节要谈到的 <code>Tracing</code> 模式,相比 Sampling,它是<code>方法级别</code>的洞察,你会看到方法的更多信息,比如:</p>
<ul>
<li>方法的调用时间</li>
<li>方法的调用次数</li>
</ul>
<p>有些人可能会好奇,方法的调用次数底层是怎么算出来的? 这个是得益于 coreclr 的 ICorProfilerCallback 通知机制,在进入方法和退出方法时,coreclr都会通知给注册的第三方(DotTrace),具体细节就不说了。</p>
<p>接下来修改为 <code>Tracing</code> 模式,重新执行程序,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202506/214741-20250620135533112-879558238.png" alt="" loading="lazy"></p>
<p>DotTrace 跟踪完成之后,会产生跟踪文件,然后用F5搜索目标函数<code>MultiplyMatrices</code> 。截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202506/214741-20250620135533134-356144111.png" alt="" loading="lazy"></p>
<p>从卦中我们获得了更多的信息,比如发现有人对 MultiplyMatrices 方法做了三次调用,总计花费近 8s,平均下来每次call 近 3s,如果觉得单次 3s 还是有点长,接下来该如何继续下钻呢?</p>
<blockquote>
<p>值得一提的是,Tracing 属于方法级作用域,生成的采样文件相对较大,适合小时级监控。</p>
</blockquote>
<h3 id="3-line-by-line-模式">3. Line-by-Line 模式</h3>
<p>刚才我们说到的 <code>Tracing</code> 属于一种方法级作用域,再往下走的话只能是 <code>语句级</code>了,它的底层主要借助了<code>IL插桩技术</code>,有一些像 harmony 的 transpiler,由于插入了大量的垃圾代码,会导致程序运行速度极度的下降,久久不能跟踪结束!所以在这种细粒度的场景下,更适合用代码实现局部跟踪,后续的文章会跟大家继续聊。</p>
<p>当我跟踪了100s后,停止跟踪,打开视图后,右键点击 MultiplyMatrices 方法查看源代码,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202506/214741-20250620135533137-872874784.png" alt="" loading="lazy"></p>
<p>从卦中可以清晰的看到,在我跟踪的100s周期内都是被MultiplyMatrices方法给吃掉了,从 <code>命中次数</code> 角度看,耗时都在三层的 for 循环中 O(N3)。其中第三层的 for 已执行了 10亿+ 次。。。</p>
<h2 id="三总结">三:总结</h2>
<p>DotTrace 是一款非常🐂👃的可视化商业工具,非常适合<code>程序突然变慢</code>的场景分析。</p>
<blockquote>
<p>作为JetBrains社区内容合作者,如有购买jetbrains的产品,可以用我的折扣码 HUANGXINCHENG,有25%的内部优惠哦。</p>
</blockquote>
<img src="https://images.cnblogs.com/cnblogs_com/huangxincheng/345039/o_210929020104最新消息优惠促销公众号关注二维码.jpg" width="700" height="300" alt="图片名称" align="center"><br><br>
来源:https://www.cnblogs.com/huangxincheng/p/18938244
頁: [1]
查看完整版本: DotTrace系列:1. 理解四大经典的诊断类型(上)