一:背景

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. AI与.NET技术实操系列(九):总结篇 ── 探讨.NET 开发 AI 生态:工具、库与未来趋势

    1. 引言 本文作为本系列的最后一篇,旨在全面探讨 .NET 生态中与 AI 相关的工具.库.框架和资源,帮助开发者了解如何在 .NET 环境中开发 AI 应用.我们将分析 Microsoft 的 A ...

  2. [源码系列:手写spring] IOC第九节:应用上下文ApplicationContext

    内容介绍 在Spring中应用上下文ApplicationContext是相较于BeanFacotry更为先进的IOC容器,BeanFacotry是Spring实现IOC最基础最核心的接口,使得Spr ...

  3. 【Ubuntu】vim-9.1.0821 编译安装

    [Ubuntu]vim-9.1.0821 编译安装 零.起因 由于 Ubuntu 库中的vim版本只有8点几,满足不了需求,故需要自己编译安装更新的版本,本文介绍如何安装更新的vim版本. 壹.操作步 ...

  4. Cursor预测程序员行业倒计时:CTO应做好50%裁员计划

    提供AI咨询+AI项目陪跑服务,有需要回复1 前两天跟几个业内同学做了一次比较深入的探讨,时间从15.00到21.00,足足6个小时! 其中有个问题特别有意思:从ChatGPT诞生到DeepSeek爆 ...

  5. ZKmall开源商城iOS 与安卓双端开发:如何平衡 B2B2C 商城的代码复用与性能

    在ZKmall开源商城的iOS与安卓双端开发中,平衡B2B2C商城的代码复用与性能是一个关键考量.以下是一些建议和实践方法,以实现这一目标: 一.架构分层设计:解耦与复用 1. 分层架构模型 merm ...

  6. 超实用!Prompt程序员使用指南,大模型各角色代码实战案例分享

    提示词(Prompt)是输入给大模型(LLM)的文本指令,用于明确地告诉大模型你想要解决的问题或完成的任务,也是大语言模型理解用户需求并生成准确答案的基础.因此 prompt 使用的好坏,直接决定了大 ...

  7. SearXNG私有化部署与Dify集成

    一.概述 SearXNG 是一个免费的互联网元搜索引擎,它聚合了来自各种搜索服务和数据库的结果,但摆脱了隐私追踪 -- 用户行为既不会被引擎跟踪也不会被分析. 功能特性 自托管,可以私有化部署 没有用 ...

  8. Docker安装elasticsearch、kibana、ik分词器

    一.下载ealastic search和kibana,两者的版本要一致 docker pull elasticsearch:7.6.2 docker pull kibana:7.6.2 二.配置 mk ...

  9. Eclipse java项目转Maven项目

    1.右键项目->configure->选择maven->配置maven的pom.xml 2.在src/main下新建java文件,将原来src下的java文件夹拷贝至该目录下: 3. ...

  10. 2025dsfz集训Day8:线段树

    Day8:线段树 前言:线段树听起来很高大尚,就是儿子节点表示法的树.几乎一样. \[Designed\ By\ FrankWkd\ -\ Luogu@Lwj54joy,uid=845400 \] 特 ...