DotTrace系列:4. 诊断窗体程序变卡之原因分析
一:背景
1. 讲故事
写这一篇是因为昨天看 dottrace 官方文档时,在评论区看到了一条不友好的评论,截图如下:
虽然语气上带有些许愤怒,但说实话人家也不是无中生有,作为 dottrace 的忠实粉丝我还是能够理解他的心情的,所以这篇我用最新的 2025.01 版 dottrace 来演示一下,时过境迁有些功能和性能基准虽然已经不一样了。
话不多说我们开始吧。
二:程序变卡分析
1. 现象描述
案例代码是一个窗体程序,它可以将上传文件的内容按行反转,比如说 abcd -> dcba
,我准备了一个 1G 的日志文件,在程序运行过程中我发现程序特别吃内存,而且在处理过程中明显发现程序卡卡的,能否帮我分析下到底怎么回事,案例代码可下载:https://github.com/DarthWeirdo/dotTrace_Timeline_Get_Started
上面的项目下载好之后,一定要改成 x64 位的,运行之后截图如下:
因果论中的 果
,现在我们知道了,接下来就是由果推因
,那怎么推呢?使用 dotTrace 哈。
2. dotTrace 分析
使用 timeline 模式对 MassFileProcessing 程序进行性能数据收集,最后的性能图表如下:
这里面有几个概念要解释:
- 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
。
- 子模块条状时间分布
从条状图
上可以看到,11s 的时间主要被 GCWait
和 WPF
这块吃掉了,前者占比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 Worker
和 Garbage Collection
线程,这里稍微提一下,2025版的 dottrace 新增了多tab页模式,这个太方便了,现在我可以多tab分析了,因为我用windbg 的时候也是这么玩的,非常利于分析加速,截图如下:
Gargage Collection
和 CLR 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. 诊断窗体程序变卡之原因分析的更多相关文章
- H5:加载原理,慢加载和卡顿原因分析,
前端H5工作原理: 请求和显示原理 H5页面卡顿原因分析: 1.动画太多:渲染重绘占用GPU 2.页面操作导致重绘频繁 3.页面元素复杂:资源类标签太多(图像/视频/dom树太长) 4.内置webvi ...
- FreeRTOS 启动进程调度后,程序卡死的部分原因分析。
现象:1,RTOS 使用时 系统卡启动文件 B .处. 原因分析:该种情况是由于定义开启了中断,但是未开启中断处理服务.程序执行到中断响应式无对应的程序响应 ...
- 为什么一个目录里放超过十个Mp4文件会导致资源管理器和播放程序变卡变慢?
最近<鬼吹灯之精绝古城>大火,我也下载了剧集放在移动硬盘里. 起初还没事,当剧集超过十个时发现资源管理器变慢了,表现为上方的绿条总是在闪动前进,给文件改名都缓慢无比. 当剧集超过十五个时, ...
- 关于windows系统DPI增大导致字体变大的原因分析
最近再学习WPF开发,其中提到一个特性“分辨率无关性”,主要功能就是实现开发的桌面程序在不同分辨率的电脑上显示时,会根据系统的DPI自动进行UI的缩放,从而不会导致应用程序的失真. 这个里面就提到了个 ...
- Android中app卡顿原因分析示例
在知乎回答了一个“为什么微博的app在iPhone比Android上流畅”的问题.后面部分是一个典型的动画卡顿的性能分析过程,因此帖在这里.有编程问题可以在这里交流.知乎链接. =========== ...
- java线程基础巩固---多Product多Consumer之间的通讯导致出现程序假死的原因分析
在上一次中已经实现一个生产者与消费者的初步模型(http://www.cnblogs.com/webor2006/p/8413286.html),但是当时只是一个生产者对应一个消费者,先贴下代码: p ...
- 在Windows下设置环境变量 运行mysql程序变得更容易
在Windows下设置环境变量,点开始菜单,右键单击我的电脑--属性--高级--环境变量 可以看到PATH的变量是这样的: C:\WINDOWS;C:\WINDOWS\COMMAND 为了让运行m ...
- 如何给windows窗体程序打包成一个安装包
http://blog.csdn.net/xyy410874116/article/details/6341787 给windows窗体程序打包成一个安装包:具体操作在:http://hi.baidu ...
- 学习java随笔第十一篇:java窗体程序
要开java的窗体程序,就要下载开发窗体的工具. 这里我用的是的myeclipse,可以直接在网上下载安装即可. 我用的是10.0版本的,如果需要汉化的话,可以看一下这篇文章:myeclipse.10 ...
- [置顶] Android学习系列-把文件保存到SD卡上面(6)
Android学习系列-把文件保存到SD卡上面(5) 一般多媒体文件,大文件需要保存到SD卡中.关键点如下: 1,SD卡保存目录:mnt/sdcard,一般采用Environment.getExter ...
随机推荐
- AI与.NET技术实操系列(九):总结篇 ── 探讨.NET 开发 AI 生态:工具、库与未来趋势
1. 引言 本文作为本系列的最后一篇,旨在全面探讨 .NET 生态中与 AI 相关的工具.库.框架和资源,帮助开发者了解如何在 .NET 环境中开发 AI 应用.我们将分析 Microsoft 的 A ...
- [源码系列:手写spring] IOC第九节:应用上下文ApplicationContext
内容介绍 在Spring中应用上下文ApplicationContext是相较于BeanFacotry更为先进的IOC容器,BeanFacotry是Spring实现IOC最基础最核心的接口,使得Spr ...
- 【Ubuntu】vim-9.1.0821 编译安装
[Ubuntu]vim-9.1.0821 编译安装 零.起因 由于 Ubuntu 库中的vim版本只有8点几,满足不了需求,故需要自己编译安装更新的版本,本文介绍如何安装更新的vim版本. 壹.操作步 ...
- Cursor预测程序员行业倒计时:CTO应做好50%裁员计划
提供AI咨询+AI项目陪跑服务,有需要回复1 前两天跟几个业内同学做了一次比较深入的探讨,时间从15.00到21.00,足足6个小时! 其中有个问题特别有意思:从ChatGPT诞生到DeepSeek爆 ...
- ZKmall开源商城iOS 与安卓双端开发:如何平衡 B2B2C 商城的代码复用与性能
在ZKmall开源商城的iOS与安卓双端开发中,平衡B2B2C商城的代码复用与性能是一个关键考量.以下是一些建议和实践方法,以实现这一目标: 一.架构分层设计:解耦与复用 1. 分层架构模型 merm ...
- 超实用!Prompt程序员使用指南,大模型各角色代码实战案例分享
提示词(Prompt)是输入给大模型(LLM)的文本指令,用于明确地告诉大模型你想要解决的问题或完成的任务,也是大语言模型理解用户需求并生成准确答案的基础.因此 prompt 使用的好坏,直接决定了大 ...
- SearXNG私有化部署与Dify集成
一.概述 SearXNG 是一个免费的互联网元搜索引擎,它聚合了来自各种搜索服务和数据库的结果,但摆脱了隐私追踪 -- 用户行为既不会被引擎跟踪也不会被分析. 功能特性 自托管,可以私有化部署 没有用 ...
- Docker安装elasticsearch、kibana、ik分词器
一.下载ealastic search和kibana,两者的版本要一致 docker pull elasticsearch:7.6.2 docker pull kibana:7.6.2 二.配置 mk ...
- Eclipse java项目转Maven项目
1.右键项目->configure->选择maven->配置maven的pom.xml 2.在src/main下新建java文件,将原来src下的java文件夹拷贝至该目录下: 3. ...
- 2025dsfz集训Day8:线段树
Day8:线段树 前言:线段树听起来很高大尚,就是儿子节点表示法的树.几乎一样. \[Designed\ By\ FrankWkd\ -\ Luogu@Lwj54joy,uid=845400 \] 特 ...