一:背景

在洞察 GC 方面,我觉得市面上没有任何一款工具可以和 PerfView 相提并论,这也是为什么我会在 WinDbg 之外还要学习这么一款工具的原因,这篇我们先简单聊聊 PerfView 到底能洞察 GC 什么东西?

二:洞察 GC

1. 到底都能看到 GC 什么?

能获取到的 GC 信息非常多,比如:

  1. 程序运行期间 GC 触发了多少次?

  2. GC 最大一次暂停耗费了多久?

  3. 每一次 GC 触发的原因是什么?

  4. GC 暂停 >200ms 的都有哪些?

  5. GC 触发 3 阶段中各个函数的耗时是怎样的?

等等。。。 可获取的信息非常多,后面的文章会逐一聊。

2. 获取 GC 的一般性信息

为了方面讲述,先上一段故意无限次拼接 string 的代码,让 GC 高频触发。


internal class Program
{
static void Main(string[] args)
{
Task.Run(Alloc1); Console.ReadLine();
} static void Alloc1()
{
var s = string.Empty; for (int i = 0; i < 100000; i++)
{
s = s + i.ToString();
}
}
}

接下来我们用 Collect -> Run 对程序采样 20s,观察这 20s 中 GC 的触发情况。

稍等20s后,在打包好的 zip 视图列表中点击 Memory -> GCStats

然后点击我们的应用程序,就会看到 GC 汇总信息。


GC Stats for Process 3848: ConsoleApp10
•CommandLine: "D:\net6\ConsoleApp1\ConsoleApp10\bin\Debug\net6.0\ConsoleApp10.exe"
•Runtime Version: V 6.0.522.21309 (built on 2022/4/14 1:31:32)
•CLR Startup Flags: 8388611
•Total CPU Time: 10,471 msec
•Total GC CPU Time: 1,305 msec
•Total Allocs : 38,099.086 MB
•GC CPU MSec/MB Alloc : 0.034 MSec/MB
•Total GC Pause: 2,710.6 msec
•% Time paused for Garbage Collection: 19.9%
•% CPU Time spent Garbage Collecting: 12.5%
•Max GC Heap Size: 14.285 MB
•Peak Process Working Set: 40.776 MB
•Peak Virtual Memory Usage: 5,003.682 MB
...

接下来看下 GC Rollup By Generation 列表,如下图所示:

从图中可得知这 20s 期间的如下信息:

  • GC触发了 1w 次,平均 500次/s,哈

  • GC总的暂停时间是 2.7s,占比 13.5% ,也就是说20s时间,程序其实就跑了 86.5% 的时间,有点像 公摊面积 哈, 其实这是有问题的。

  • 1w 次GC 中,最大的单次暂停时发生在 gen0 上,耗费了 2.6ms。

还有一个比较有用的是 Pause > 200 Msec GC Events 列表,它记录着那些暂停时间大于 >200ms 的单次GC,截图如下:

还好本次实验没有遇到,不过可以肯定的是,大于 200ms 的程序暂停肯定会导致明显的卡顿,所以仔细研读下这些 列信息 非常有用,是不是因为 存活对象过多 导致的 mark_phase 和 compact_phase 时间过长? 还是因为 pinned 或者 碎片化过多导致 plan_phase 时间过长等等,具体问题具体分析。

接下来就是这 10956 次 GC 的 detail 信息啦,可以观察到 GC 的触发原因,触发了哪一代 GC,SuppendEE 暂停时间,GC 暂停时间,GC 与上次 GC 之后的一个时间占比 等等各种信息,大家可以把鼠标放在标头上,都有相应的提示。

稍微提醒下就是 Pause Start 是没有解释的,其实它是一个毫秒时间戳,如果你仔细观察,也就是 500次/s 的排序记录。

3. 获取 GC 内部函数的 一般性耗时

熟悉 GC 的朋友应该知道,最简单的非并发工作站模式 GC 是这样的一个触发模式,如下代码所示:


GarbageCollectGeneration()
{
SuspendEE();
garbage_collect();
RestartEE();
} garbage_collect()
{
generation_to_condemn();
gc1();
} gc1()
{
mark_phase();
plan_phase();
} plan_phase()
{
// actual plan phase work to decide to
// compact or not
if (compact)
{
relocate_phase();
compact_phase();
}
else
make_free_lists();
}

那如何用 PerfView 去检测这些函数的耗时呢?可以是可以,但只是一个大体的模型,因为原理是对 CPU 进行采样 再根据权重算出来的,所以有一定的参考意义。

接下来我们在 Find 输入框中搜索 coreclr!GarbageCollectGeneration 关键词。

接下来右键选择 Goto -> Goto Item in Callees 选项,然后展开可能的耗时,如下图所示:

从输出信息看, GarbageCollectGeneration 大概会耗时 12.2ms ,耗时比较长的两块大概是:

  1. GCScanRoots

在标记阶段,爬线程栈耗时相对较大,耗费了 3.8ms,继续往下翻的话,会发现都耗费在寻找 GC 安全点上。

  1. relocate_phase

这个属于 计划阶段 的重定位阶段,主要用来修改需要移动对象的新地址,所谓 兵马未动,粮草先行

关于 GC 的洞察,还有更多好玩的东西,放在后续文章聊吧!

PerfView专题 (第六篇):如何洞察 C# 中 GC 的变化的更多相关文章

  1. PerfView专题 (第八篇):洞察 C# 内存泄漏之寻找静态变量名和GC模式

    一:背景 这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块: 洞察内存泄漏中的静态大集合变量名. 验证 ...

  2. PerfView专题 (第十篇):洞察 C# 终结队列引发的内存泄漏

    一:背景 C# 程序内存泄漏的诱发因素有很多,但从顶层原理上来说,就是该销毁的 用户根 对象没有被销毁,从而导致内存中意料之外的对象无限堆积,导致内存暴涨,最终崩溃,这其中的一个用户根就是 终结器队列 ...

  3. PerfView专题 (第七篇):如何洞察触发 GC 的 C# 代码?

    一:背景 上一篇我们聊到了如何用 PerfView 洞察 GC 的变化,但总感觉还缺了点什么? 对,就是要跟踪到底是什么代码触发了 GC,这对我们分析由于 GC 导致的 CPU 爆高有非常大的参考价值 ...

  4. PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量

    一:背景 去年 GC架构师 Maoni 在 (2021 .NET 开发者大会) [https://ke.segmentfault.com/course/1650000041122988/section ...

  5. PerfView专题 (第九篇):洞察 C# 中的 LOH 内存碎片化

    一:背景 在 内存泄漏 的系列问题中,有一类问题是 内存碎片化 导致的,而且这种更容易发生在 LOH 上,因为它默认不开启 对象压缩,一般遇到这种情况,优先让朋友执行下面的代码应急. GCSettin ...

  6. PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏

    一:背景 上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc 或者 C++ 的 new 分配而不释放所造成的,这一篇我们来聊一下由 ...

  7. PerfView专题 (第四篇):如何寻找 C# 中程序集泄漏

    一:背景 前两篇我们都聊到了非托管内存泄漏,一个是 HeapAlloc ,一个是 VirtualAlloc,除了这两种泄漏之外还存在其他渠道的内存泄漏,比如程序集泄漏,这一篇我们就来聊一聊. 二: 程 ...

  8. PerfView专题 (第五篇):如何寻找 C# 托管内存泄漏

    一:背景 前几篇我们聊的都是 非托管内存泄漏,这一篇我们再看下如何用 PerfView 来排查 托管内存泄漏 ,其实 托管内存泄漏 比较好排查,尤其是用 WinDbg,毕竟C#是带有丰富的元数据,不像 ...

  9. python学习之【第十六篇】:Python中的常用模块之OS模块、sys模块、random模块

    1. OS模块 OS模块是与操作系统交互的一个接口.内部提供了以下方法: os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname& ...

随机推荐

  1. goose消元

    ps.改了标题 魔板 思路:按序消除变量,用当前行(i)[行i消\(x_i\)元素],消后面的每一行的i元素 最后按逆序回代值 注意若有i~n行i元素系数都为0说明没有唯一解(其余x的解跟i元素有关) ...

  2. 2.1 动为进程,静为程序 -进程概论 -《zobolの操作系统学习札记》

    2.1 动为进程,静为程序 -进程概论 目录 2.1 动为进程,静为程序 -进程概论 问1:发明进程的原因? 问2:现在计算机中的进程的定义是什么? 问3:为什么进程跟处理器的联系更密切? 问4:进程 ...

  3. 【Java面试】介绍下Spring IoC的工作流程

    Hi,我是Mic 一个工作了4年的粉丝,在面试的时候遇到一个这样的问题. "介绍一下Spring IOC的工作流程" 他说回答得不是很好,希望我能帮他梳理一下. 关于这个问题,我们 ...

  4. UiPath鼠标操作文本的介绍和使用

    一.鼠标(mouse)操作的介绍 模拟用户使用鼠标操作的一种行为,例如单击,双击,悬浮.根据作用对象的不同我们可以分为对元素的操作.对文本的操作和对图像的操作 二.鼠标对文本的操作在UiPath中的使 ...

  5. Pyinstaller打包pikepdf失败的问题排查

    问题 最近在项目里用到了pikepdf这个库,用于实现pdf水印插入的一个小功能,源码调试阶段运行一切OK但是在出包时报了如下异常. Traceback (most recent call last) ...

  6. sql-关键词的大小写与注释

    是否区分大小写 和 注释 大小写 oracle 自带的sqlplus: mysql 客户端 : Navicat: 注释 oracle 自带的sqlplus: mysql 客户端 : 小节 oracle ...

  7. Linux下修改RabbitMQ密码

    1,首先查看用户列表 rabbitmqctl list_users 2,修改对应用户密码 其中username 为用户名, newpasswd为新密码 rabbitmqctl change_passw ...

  8. VScode运行总是显示running状态

    一.每次点击运行都显示code is already running,而且键盘也没有办法输入 二.解决办法 注意:记得重新启动VScode

  9. 编译调试Net6源码

    前言 编辑调试DotNet源码可按照官网教程操作,但因为网络问题中间会出现各种下载失败的问题,这里出个简单的教程(以6为版本) 下载源码 下载源码 GitHub下载源码速度极慢,可替换为国内仓库htt ...

  10. 全国气象数据/降雨量分布数据/太阳辐射数据/NPP净初级生产力数据/植被覆盖度数据

    ​        气象数据一直是一个价值较高的数据,它被广泛用于各个领域的研究当中.气象数据包括有气温.气压.相对湿度.降水.蒸发.风向风速.日照等多种指标,但是包含了这些全部指标的气象数据却较难获取 ...