一:背景

1. 讲故事

写这一篇是因为昨天看 dottrace 官方文档时,在评论区看到了一条不友好的评论,截图如下:

虽然语气上带有些许愤怒,但说实话人家也不是无中生有,作为 dottrace 的忠实粉丝我还是能够理解他的心情的,所以这篇我用最新的 2025.01 版 dottrace 来演示一下,时过境迁有些功能和性能基准虽然已经不一样了。

话不多说我们开始吧。

二:程序变卡分析

1. 现象描述

案例代码是一个窗体程序,它可以将上传文件的内容按行反转,比如说 abcd -> dcba,我准备了一个 1G 的日志文件,在程序运行过程中我发现程序特别吃内存,而且在处理过程中明显发现程序卡卡的,能否帮我分析下到底怎么回事,案例代码可下载:https://github.com/DarthWeirdo/dotTrace_Timeline_Get_Started

上面的项目下载好之后,一定要改成 x64 位的,运行之后截图如下:

因果论中的 ,现在我们知道了,接下来就是由果推因,那怎么推呢?使用 dotTrace 哈。

2. dotTrace 分析

使用 timeline 模式对 MassFileProcessing 程序进行性能数据收集,最后的性能图表如下:

这里面有几个概念要解释:

  1. UI Freeze

简而言之就是如果 UI 超过 200ms 都不响应用户,就属于 Freeze,可能有些朋友比较懵,上一段代码参考如下:


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
MSG msg; // Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} return (int) msg.wParam;
}

这里的 Freeze 原因有很多,常见的有如下三种:

  • 由于queue队列积压,导致用户投送过来的消息,GetMessage 在 200ms 内无法及时取到。
  • 由于用户投送过来的是长耗时任务,导致 DispatchMessage 在 200ms 内无法处理完。
  • 由于gc触发,导致 UI 被 Suspend >200 ms。

再回头观察上面的面板,可以看到冻结时间高达 UI Freeze =11.4s

  1. 子模块条状时间分布

条状图上可以看到,11s 的时间主要被 GCWaitWPF 这块吃掉了,前者占比50%,后者占比43%,接下来稍微解释下这两个词的概念:

  • GCWait 当前线程等待其他线程GC操作完成而处于的一种等待状态,比如下面的截图:

  • WPF UI处理相关的业务逻辑,比如UI更新等等。

接下来我们就要逐个分析这两块了。

3. 为什么GC Wait 高达 50%

观察 Main 时间轴上可以看到有很多间断的 灰色区域,这些灰色区域即是所谓的暂停(GC wait),截图如下:

根据CLR的相关知识,只有两种原因会导致 GC Wait 产生。

  • 完整的 blocking GC,即著名的 STW 机制。
  • background gc 三阶段中的 blocking 阶段,这块我的训练营里说的很细。

有了这个思路,接下来就是观察到底是哪个线程触发的,在 Visible Threads 中按需选择线程,这里我就勾上 CLR WorkerGarbage Collection 线程,这里稍微提一下,2025版的 dottrace 新增了多tab页模式,这个太方便了,现在我可以多tab分析了,因为我用windbg 的时候也是这么玩的,非常利于分析加速,截图如下:

Gargage CollectionCLR Worker 都出来后,缩小时间轴,宏观的观测下Main灰色和其他线程的深蓝色,截图如下:

从卦中可以清晰的看到很多的Main阻塞看起来是 CLR Worker 触发GC导致的,那是不是的呢?观察一下便知哈,将时间轴稍微调整下,选择 Flame Graph 火焰图,从中就有 plan_phase,这是GC三阶段中经典的 计划阶段,截图如下:

接下来就要思考了,为什么会触发那么多次GC,这些GC是大GC还是小GC呢?要调查这个原因,可以单独勾选 CLR Worker 线程,可以看到如下信息:阻塞式GC高达 92.7% 同时 1代GC 高达 71.7%,截图如下:

根据dump分析经验,看样子 UI 卡卡的和过频的GC有关。GC触发本质是要到gc堆上捞垃圾,所以肯定有人在不断的丢垃圾,所以从这个角度继续突破,选择 .NET Memory Allocation 事件,然后观察 hotspots 区域,可以看到 总计 12G 的内存分配,Reverse方法就独吞 4.9G,说明还是非常吃内存的,截图如下:

点击源代码观察,参考如下:


internal class StringReverser
{
private readonly string _original; public StringReverser(string original)
{
_original = original;
} public string Reverse()
{
char[] charArray = _original.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}

从卦中可以看到这块会产生很多的临时 char[] 和 string 对象,在1G日志的加持下导致GC频繁触发,在后续版本优化中这块是一个非常重要的点,可能你需要用 ArrayPool 或者 Span 等机制来减少临时对象的过多产生。

4. 为什么 WPF 高达 42%

WPF占比过高其实也意味着更新UI的操作比较频繁,这种情况也会导致程序在响应UI方面会有所延迟,那到底是谁在频繁的更新 UI呢?

可以勾选上 Running , WPF 等选项,然后观察 火焰图,截图如下:

从卦中的火焰图中的 Dispatcher.ProcessQueue() 方法可以判断当前真的是频繁的更新UI,因为WPF在忙碌的处理队列,而它的发起者正是 ProcessInProgress 方法,观察方法的源码,参考如下:


private void ProcessInProgress(object sender, ProgressChangedEventArgs e)
{
var upd = (ProgressUpdater)e.UserState;
lblProgress.Content = $"File {upd.CurrentFileNmb} of {upd.TotalFiles}: {e.ProgressPercentage}%";
} private void ProcessFiles(object sender, DoWorkEventArgs evts)
{
try
{
_updater.TotalFiles = FilePaths.Count; for (var i = 0; i < FilePaths.Count; i++)
{
EmKeyPress(); _updater.CurrentFileNmb = i + 1; var path = FilePaths[i];
_lines = File.ReadAllLines(path); for (var j = 0; j < _lines.Length; j++)
{
var line = _lines[j];
var stringReverser = new StringReverser(line);
_lines[j] = stringReverser.Reverse(); if (j % 5 == 0)
{
var p = (float)(j + 1) / _lines.Length * 100;
Worker.ReportProgress((int)p, _updater);
}
} File.WriteAllLines(path, _lines);
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}

从卦中代码可以看到,原来 if (j % 5 == 0) 就会通过 Worker.ReportProgress((int)p, _updater); 报告进度进而触发 ProcessInProgress 方法。

找到问题之后,优化就相对简单了,将 if (j % 5 == 0) 5 改成更大一些即可,比如 1000,5000。

三:总结

用 dottrace 分析这类程序变慢的问题,真的再适合不过,这篇文章主要还是对那个不友好评论的回应和修正吧。

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

DotTrace系列:4. 诊断窗体程序变卡之原因分析的更多相关文章

  1. H5:加载原理,慢加载和卡顿原因分析,

    前端H5工作原理: 请求和显示原理 H5页面卡顿原因分析: 1.动画太多:渲染重绘占用GPU 2.页面操作导致重绘频繁 3.页面元素复杂:资源类标签太多(图像/视频/dom树太长) 4.内置webvi ...

  2. FreeRTOS 启动进程调度后,程序卡死的部分原因分析。

    现象:1,RTOS  使用时 系统卡启动文件               B       .处. 原因分析:该种情况是由于定义开启了中断,但是未开启中断处理服务.程序执行到中断响应式无对应的程序响应 ...

  3. 为什么一个目录里放超过十个Mp4文件会导致资源管理器和播放程序变卡变慢?

    最近<鬼吹灯之精绝古城>大火,我也下载了剧集放在移动硬盘里. 起初还没事,当剧集超过十个时发现资源管理器变慢了,表现为上方的绿条总是在闪动前进,给文件改名都缓慢无比. 当剧集超过十五个时, ...

  4. 关于windows系统DPI增大导致字体变大的原因分析

    最近再学习WPF开发,其中提到一个特性“分辨率无关性”,主要功能就是实现开发的桌面程序在不同分辨率的电脑上显示时,会根据系统的DPI自动进行UI的缩放,从而不会导致应用程序的失真. 这个里面就提到了个 ...

  5. Android中app卡顿原因分析示例

    在知乎回答了一个“为什么微博的app在iPhone比Android上流畅”的问题.后面部分是一个典型的动画卡顿的性能分析过程,因此帖在这里.有编程问题可以在这里交流.知乎链接. =========== ...

  6. java线程基础巩固---多Product多Consumer之间的通讯导致出现程序假死的原因分析

    在上一次中已经实现一个生产者与消费者的初步模型(http://www.cnblogs.com/webor2006/p/8413286.html),但是当时只是一个生产者对应一个消费者,先贴下代码: p ...

  7. 在Windows下设置环境变量 运行mysql程序变得更容易

    在Windows下设置环境变量,点开始菜单,右键单击我的电脑--属性--高级--环境变量 可以看到PATH的变量是这样的: C:\WINDOWS;C:\WINDOWS\COMMAND   为了让运行m ...

  8. 如何给windows窗体程序打包成一个安装包

    http://blog.csdn.net/xyy410874116/article/details/6341787 给windows窗体程序打包成一个安装包:具体操作在:http://hi.baidu ...

  9. 学习java随笔第十一篇:java窗体程序

    要开java的窗体程序,就要下载开发窗体的工具. 这里我用的是的myeclipse,可以直接在网上下载安装即可. 我用的是10.0版本的,如果需要汉化的话,可以看一下这篇文章:myeclipse.10 ...

  10. [置顶] Android学习系列-把文件保存到SD卡上面(6)

    Android学习系列-把文件保存到SD卡上面(5) 一般多媒体文件,大文件需要保存到SD卡中.关键点如下: 1,SD卡保存目录:mnt/sdcard,一般采用Environment.getExter ...

随机推荐

  1. 【SpringCloud】Gateway新一代网关

    Gateway新一代网关 概述简介 官网 上一代zuul 1.x https://github.com/Netflix/zuul/wiki 当前gateway https://cloud.spring ...

  2. $.ajax jsonp parsererror

    场景重现 通过$.ajax()发起的跨越请求代码如下: $.ajax({ dataType: "JSONP", type: "GET", url: " ...

  3. 不同数据库Oracle、PostgreSQL、Vertical、Mysql常用操作

    不同数据库Oracle.PostgreSQL.Vertical.Mysql常用操作 授权语句用于管理数据库用户的权限,常见的授权语句如下: 1.授权用户对表的SELECT权限 GRANT SELECT ...

  4. App自动化环境部署

    1.所需工具 Android-SDK:自行百度下载 Appium-Desktop:自行百度下载 真机或模拟器:自行准备 2.部署步骤 1)配置Android-SDK 解压Android-SDK压缩包 ...

  5. v-bind,v-if,v-for,v-on,v-model基本用法

    总结: 1.v-bind绑定数据:标签属性v-bind:title='xxx',简写:title='xxx', 标签内容{{xxx}} <span :title='message'>{{m ...

  6. Mybatis的动态SQL的语句

    例子. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC &quo ...

  7. 如何使用 MySQL 的 EXPLAIN 语句进行查询分析?

    如何使用 MySQL 的 EXPLAIN 语句进行查询分析? EXPLAIN 是 MySQL 提供的分析 SQL 查询执行计划的工具,用于了解查询语句的执行过程,帮助优化查询性能. 1. EXPLAI ...

  8. MacOS v15.X安装HP旧款打印机驱动(P1606dn为例)

    一.下载官方驱动 先去官网下载一下HP提供的Mac下的驱动合集(图1),可惜的只支持15.0以下版本安装. https://support.hp.com/cn-zh/drivers/hp-laserj ...

  9. 关于Bevy中的原型Archetypes

    认识Bevy中的原型 Bevy是基于ECS(Entity-Component-System)架构的游戏引擎,其中的Entity实体是游戏中的一个基本对象,但实体本身通常只是一个标识id,它不包含任何具 ...

  10. Python 3.14 t-string 要来了,它与 f-string 有何不同?

    Python 最近出了个大新闻:PEP-750 t-string 语法被正式采纳了! 这意味着 Python 将在今年 10 月发布的 3.14 版本中引入一种新的字符串前缀 t,称为模板字符串(Te ...