一切都是有其程度,是吧?车辆只有开这么快,过程只有应用这么多内存,程序猿只有喝这么多现磨咖啡。大家的生产效率遭受网络资源的限定,大家有工作能力更强或更差地运用他们。尽量贴近其極限应用咱们的每一种資源是人们的总体目标,大家期待应用咱们的 CPU 和内存的每一点,不然大家会为价格昂贵的设备多付费。
殊不知,若是人们采用了过量的資源,大家就会有也许造成性能问题、服务项目不能用问题和程序流程服务器宕机底奔溃问题。开发软件看起来简易,但一旦碰到性能问题,便会变的十分繁杂,这就是我们今日要探讨的內容。
界定最好标准
使我们试着叙述大家的最好应用软件个人行为。假定人们有很多服务器设备必须解决高运输量的要求。为简洁考虑,使我们临时忘掉高峰期時间或礼拜天。大家的服务器负荷在一天中的全部時间都多多少少同样。大家为这种服务器设备付款了很多钱,大家想要从他们那边得到尽量多的使用价值,这代表着解决尽量多的要求。依照大家对简易性的服务承诺,大家还假定服务器仅应用内存和 CPU 来解决上述要求,而且沒有别的短板,例如慢速度互联网或锁争用。
在所表述的情景中,大家的最好个人行为是在一切已知時间应用尽量多的 CPU 和内存,对不对?那样,大家可以用越来越少的设备来解决同样数目的要求。可是您很有可能不愿运用这种自然资源中的 99.9%,由于负荷的轻度提升很有可能会造成性能问题、服务器奔溃、内容丢失和别的让人烦恼的问题。因此大家应当挑选一个有充足缓存问题的标值。均值 85% 或 90% 的 CPU 和内存使用率听起来是合理的。
大家需要最先优化哪些?
大家的应用软件并不是为公平运用 CPU 和内存而搭建的。或是到它代管的机器设备的准确限定。因而,您最先应当查询的是您的服务器是CPU-bound或是Memory-bound。当服务器受 CPU 限定时,这代表着服务器可以解决的货运量遭受其 CPU 的限定。也就是说,假如您试着解决大量要求,CPU 将在其它資源(如内存)做到其限定以前做到 100%。一样的逻辑性也适用Memory-bound服务器。服务器的货运量将遭受它可以分配的内存的限定,当试着解决大量负荷时,在其它資源(如 CPU)做到其限定以前,该内存将做到 100%。
也有别的資源可以限定服务器,例如I/O,在这样的情况下,货运量会遭受硬盘或互联网的载入或写入限定。可是人们将在这篇文章中忽视这一点,开朗地假定大家的 I/O 是迅速且无尽的。
一旦你了解是啥限定了你的服务器的性能,你也就会了解最先要试着和优化哪些。假如您的服务器受 CPU 限定,那麼优化内存应用没有意义,因为它不容易提升解决的货运量。实际上,它有可能会危害货运量,由于您也许会由于越来越多的 CPU 使用率而提升内存利用率。针对内存受到限制的服务器也是如此,在这样的情况下,您需要在查询 CPU 以前优化内存应用。
精确测量 .NET 服务器中的 CPU 和内存耗费
CPU 和内存的具体精确测量非常简单的是应用Performance Counters[1]进行。CPU 利用率的标准是Process | % CPU時间。内存有几个指标值,但我建议查询Process | 私有化字节数。您很有可能还对.NET CLR 内存有兴趣 | # 意味着代管内存的全部堆中的字节数(CLR 占用的一部分,而非是全部内存,即代管 远程服务器内存)。
要查询性能计数,您可以在 Windows 电子计算机上应用Process Explorer[2]或 PerfMon,或是在 .NET Core 服务器上应用dotnet-counters 。[3]假如您的应用软件布署在云间,您可以应用像Application Insights[4](Azure Monitor[5]的一部分)那样的 APM 专用工具来表明这种信息内容。或是,您可以在编码中获得性能计数值并每 10 秒上下纪录一次,应用Azure 数据信息任务管理器[6]之类的道具在数据图表中展示数据信息。
提醒:查验设备级指标值和过程级指标值。您也许会看到别的过程已经限定您的性能。
一旦明确了什么資源限定了您的 .NET 服务器,就该优化该資源耗费了。假如您受 CPU 限定,使我们降低 CPU 利用率。假如您受内存限定,使我们降低内存需求量。
最少假如您在云间运作,一种简易的办法是变更设备规格型号。假如您受内存限定,请提升内存。假如您受 CPU 限定,请提升核心总数或得到快速的 CPU。这将提升成本费,但在这之前,您可以查验一些非常容易完成的总体目标,以优化 CPU 或内存耗费。在变更设备规格型号以前试着开展这种优化,由于优化后一切都是会更改。您也许会优化 CPU 利用率并越来越受内存限定。随后优化内存应用并再度变成 CPU 密集式。因而,假如您想防止迫不得已持续变更设备資源以融入全新的优化,最好是把它留在最终。
因此使我们谈一谈一些内存优化。
优化内存应用
有很多方式 可以优化 .NET 中的内存应用。深层次探讨他们必须一一本书,并且早已有好几本了。但我能尽可能让你一些方位和念头。
1. 掌握哪些占用了你的内存
试着优化内存时,您应当做的第一件事是掌握全局性。哪些占用了绝大多数内存?有什么基本数据类型?他们分配在哪儿?他们会在记忆里滞留多长时间?
有几种专用工具可以获得此信息内容:
- 捕获存贮文档[7]并应用内存解析器[8]或WinDbg[9]开启它。
- 应用新的GC 存贮[10](.NET Core 3.1 ) 并应用 Visual Studio 开展调研。
- 捕获堆快照更新并应用内存解析器[11]、PerfView[12]或Visual Studio 确诊专用工具[13]对它进行探寻。
此剖析将表明什么目标占用了您的绝大多数内存。假如你发觉它被采用了MyProgram.CustomerData那么就更强了。但通常,较大的目标种类是string、byte[]或byte[][]。因为应用软件中的几乎所有的信息可以应用这种种类,因而您必须寻找引入他们的人。因此,查询所占用的多元性内存(别名保存内存)很重要。这一指标值不但包含目标自身占用的内存,还包含它引入的目标占用的内存。例如,您也许会看到它MyProgram.Inventory.Item自身并不占用过多内存,但它引入了一个byte[]它储存内存中的图象并占用达到 70% 的内存。上边叙述的全部专用工具都能够表明包括较多字节数的另一半和到 GC 根的引入途径(也就是到根的最短路径算法[14])。
2. 掌握谁把内存放到了哪儿
找到谁引入了最高的内存块非常好,但这也许还不够。有时候您须要了解这种内存是怎样分配的。您将会从引入途径中了解,一些占用绝大多数内存的目标坐落于缓存文件中,但谁将他们放到那边?来源于单独时间点的内存快照更新没法给予该回答。因此,您必须分配局部变量追踪。解析器使您可以统计您的应用软件并在每一次分配时储存启用局部变量。例如,您也许会发觉建立有什么问题MyProgram.Inventory.Item目标的步骤将他们分配到启用局部变量App.OnShowHistoryClicked | App.SeeItemHistory | App.GetItemFromDatabase中。
要得到分配局部变量,您可以:
- 应用商业服务内存解析器来表明分配[15]。
- 应用 PerfView 的 GC Heap [] Stacks 之一
分配让您全方位掌握占用绝大多数内存的具体内容及其它是怎样发生的。一旦你知道这一点,你也就可以逐渐激光切割较大的块并优化他们以降低内存应用。
3.查验内存泄露
在 .NET 中造成内存泄露很容易。拥有充足多的泄露,内存耗费会伴随時间的变化而提升,你能碰到各式各样的问题。内存短板便是其中之一,但因为 GC 工作压力,您最后也会碰到 CPU 问题。
当您不会必须目标但因为种种原因他们依然被引入而且垃圾收集器始终不容易释放出来他们时,便会产生内存泄露。产生这种现象的缘故[16]有很多。
要掌握您是不是有明显的内存泄露,请查询一段时间内的内存耗费数据图表(进程 | 私有化字节数计数)。假如内存一直在提升,而沒有偏移某一水准,则有可能存有内存泄露。
应用内存解析器调节泄露[17]非常简易。
4. 转换到 GC 工作平台方式
.NET 中有几种垃圾收集器模式。关键的2种方式是Workstation GC和Server GC。Workstation GC 对于更短的 GC 中止和更快的易用性开展了提升,特别适合桌面上应用程序。网络服务器 GC 具备更长的 GC 暂停时间,而且对于更高一些的货运量开展了提升。在 Server GC 方式下,应用程序可以在垃圾回收利用中间解决大量数据信息。
网络服务器 GC 为每一个 CPU 关键建立不一样的代管堆。这代表着不一样的 X 代内存室内空间必须更长的时间段才可以铺满,因而内存耗费会更高一些。您大部分是在用内存获得货运量。从 GC 网络服务器方式(.NET 网络服务器的自定义方式)更改成 GC 工作平台方式将降低内存需求量。这在要求负荷太重的中小型应用程序中可能是有效的。可能在与主应用程序一起工作的 IIS 服务器中的輔助进程中。
Sergey Tepliakov[18]对于此事有一篇非常好的文章内容。
5.查验你的缓存
在第 1 步以后,您应当能见到什么目标占有了您的内存,但我觉得着重强调缓存。每每牵涉到高内存耗费时,依据我的工作经验,它一直最后变成内存泄露或缓存。
缓存好像是很多问题的奇妙解决方法。当您可以将結果存放在内存中并再次应用它时,为什么要实行2次?可是缓存是有成本的。一个简便的建立会将目标始终储存在内存中。您应当按时间限制或以其它方法使缓存失效。缓存还会继续将临时性目标留到内存中相比较长的時间,这会造成大量的 Gen 1 和 Gen 2 搜集,从而造成GC 工作压力[19]。
下列是一些提升内存缓存的念头:
- 应用.NET 中的目前缓存完成[20]可以轻轻松松建立无效对策。
- 考虑到为一些事挑选不缓存。您也许会用 CPU 或 IO 获得内存,可是当您遭受内存限定时,您应当如此做。
- 考虑到应用内存不够缓存。这可能是将数据信息保留在文档或本地数据库中。或是采用像Redis[21]那样的分布式系统缓存解决方法。
6.按时启用GC.Collect()
这条提议是违背判断力的,由于最佳的作法是始终不必启用GC.Collect(). 垃圾收集器很聪慧,它应当自身了解什么时候开启搜集。但问题是垃圾收集器只考虑到自身的进程。假如它沒有非常的内存,它会当心开启搜集并空出室内空间。但假如它的确有充足的内存,GC 会十分愿意承受太多的内存耗费。因而,GC 的自私自利天性可能是日常生活在同一台设备上的别的进程的问题,很有可能代管在同一个 IIS 上。这类不必要的内存很有可能会致使别的进程迅速地做到他们的極限,或是造成他们自己的垃圾收集器更为勤奋地工作中,由于他们很有可能不正确地觉得他们将要耗光内存。
您也许会觉得,假如别的进程的 GC 会做到觉得大家内存不够并因而更为勤奋地工作中的水平,那麼我们自己的进程也会那样觉得并开启垃圾搜集来解决困难。但我们不能作出那样的假定。一方面,这种进程很有可能运作不一样的 GC 完成版本号(由于不一样的 CLR 版本号)。除此之外,您有不一样的应用程序个人行为可以使 GC 以不一样的方法工作中。例如,一个进程很有可能会以更好的速度分派内存,因而 GC 将迅速地逐渐“注重”可以用内存。道德底线是手机软件很艰难,如果你在一台设备上面有好几个进程时,如同 IIS 一样,你需要充分考虑这一点,并很有可能采用一些不寻常的流程。
提升 CPU 利用率
钱币的另一面是 CPU 利用率。一旦您发觉 CPU 是应用程序货运量的短板,就要做许多事儿。
1. 剖析您的应用程序
提升 CPU 的第一步是掌握它。到底是什么因素导致的?什么方式承担?什么要求是较大的 CPU 耗费者,什么是总流量?这一切都能够根据剖析应用程序来处理。
剖析容许您纪录实行范畴并表明全部被启用的办法及其他们在纪录期内采用了是多少 CPU。解析器通常容许将那些結果视作一般目录、启用树乃至火焰图。
这也是 PerfView 中的简易目录主视图:
这也是同样情景的火焰图:
您可以利用下列方法剖析您的运用:
- 假如情景在当地再现,请性能指标解析器,如PerfView[22]、dotTrace[23]、ANTS perf profiler[24],或在您的开发设计电子计算机上应用 Visual Studio 。[25]
- 在工作环境中,非常简单的统计分析方法是应用应用程序特性监管 (APM) 专用工具,例如Azure Application Insights profiler[26]或RayGun[27]。
- 您可以根据将代理商拷贝到生产机器并纪录快照更新来研究沒有 APM 的工作环境。应用 PerfView,您应当拷贝全部程序流程。它结构紧凑,不用安裝。应用 dotTrace,您可以拷贝容许在制造中纪录快照更新的轻量代理商。[28]
- 在 .NET Core 3.0 应用程序中,您可以安裝 .NET Core 3.0 SDK 并应用 dotnet-trace 命令行工具纪录快照更新[29],随后应用 PerfView 将其拷贝到开发设计设备并做好剖析。
2.查验垃圾收集器的运用状况
想对你说提升 .NET CPU 应用最重要的一点是合理的内存管理方法。在这方面要问的主要问题是:“垃圾搜集消耗了是多少 CPU?”。GC 的工作方式是在搜集期内,您的实行进程被冻洁。这代表着垃圾搜集立即影响到特性。因而,假如您受 CPU 限定,我建议查验的第一件事是特性计数[30]。NET CLR 内存 | % GC 時间。
我不能让你一个标示问题的神奇数字,但依据工作经验,当这一值超出 20% 时,你也许会碰到问题。假如超出 40%,那麼你毫无疑问有什么问题。如此高的百分数说明 GC 工作压力,而且有方法解决它[31]。
3.应用二维数组和目标池来器重内存
列阵的划分和必然的消除分派很有可能十分价格昂贵。高频实行这种分派会导致 GC 工作压力并耗费很多 CPU 時间。处理这个问题的一个好方法是应用内嵌的ArrayPoolObjectPool ([32]仅限于 .NET Core)。这一念头非常简单。为二维数组或目标分派一个共享资源缓冲区域,随后在没有分派和撤销分派新内存的情形下多次重复使用。这是一个简易的应用实例ArrayPool:
{
var pool = ArrayPool<int>.Shared;
int[] array = pool.Rent(ArraySize);// do stuf
pool.Return(array);
}
4. 转换到 GC 网络服务器方式
大家早已探讨过迁移到GC 工作平台方式[33]以节约运行内存。但假如您受 CPU 限定,请考虑到转换到网络服务器方式以节约 CPU。衡量是网络服务器方式以大量运行内存为成本容许更高一些的货运量。因而,假如您维持同样的货运量,您最后将节约 CPU 時间,不然废弃物搜集会耗费这种時间。
默认设置状况下,.NET 网络服务器很可能具备 GC 网络服务器方式,因而很有可能不用此变更。可是很有可能有些人以前将其更改成工作平台方式,在这样的情况下,您应当当心将其变更回家,由于她们将会有充足的原因。
变更时,请尽量监管运行内存耗费和 GC 中的 % Time。您很有可能想查询第 2 代利用率,但要是这些数据很高,它将体现在更多的 GC 時间百分数中。
5.查验别的过程
当尝试将您的网络服务器充分发挥到最好極限时,您很有可能要想完全掌握它,这代表着绝不能放弃存有于您的过程以外的问题。极有可能别的过程不时耗费一堆CPU,并造成一段时间的功能降低。这种可能是您在 IIS 上布署的其它应用软件、按时 Web 工作、由电脑操作系统开启的物品、防程序流程或别的一千百种物品。
对于此事开展研究的一种方式是应用 PerfView 纪录全部体系中的 ETW 事情。PerfView 从
全部
过程中捕获 CPU 局部变量。您可以以不大的特性花销运作它很长期。您可以在做到某一 CPU 峰值时全自动终止搜集[34]并完成发掘。您也许会对效果觉得诧异。
汇总
我认为,从由上而下的方面解决规模性的性能问题是让人入迷的。您很有可能有一个精英团队耗费数月時间提升一段编码,比较之下,资源配置的简易变更将造成更多的危害。并且,假如您的业务流程充足大,那麼这一细微的改变便会转变成为一大笔钱。你还记得在你的协议中规定一个提成条文吗?不管怎样,希望这篇文章对你有效,假如你看到了,你也许会对我的书Practical Debugging for .NET 开发人员[35]有兴趣,我还在在其中深层次探讨了性能和内存问题的问题清除。
References
[1] Performance Counters: https://michaelscodingspot.com/performance-counters/
[2] Process Explorer: https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer
[3] dotnet-counters 。: https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters
[4] Application Insights: https://docs.microsoft.com/en-us/azure/cloud-services/diagnostics-performance-counters#application-insights
[5] Azure Monitor: https://azure.microsoft.com/en-us/services/monitor/#overview
[6] Azure 数据信息任务管理器: https://azure.microsoft.com/en-us/services/data-explorer/#getting-started
[7] 捕获转储文档: https://michaelscodingspot.com/how-to-create-use-and-debug-net-application-crash-dumps-in-2019/
[8] 内存分析器: https://memprofiler.com/online-docs/manual/importmemorydumpfiles.html
[9] WinDbg: https://michaelscodingspot.com/how-to-create-use-and-debug-net-application-crash-dumps-in-2019/#Investigate-Dumps-with-WinDbg
[10] GC 转储: https://devblogs.microsoft.com/dotnet/collecting-and-analyzing-memory-dumps/
[11] 内存分析器: https://michaelscodingspot.com/memory-profilers-principles#snapshots
[12] PerfView: https://bennettadelson.wordpress.com/2013/04/11/using-perfview-to-diagnose-a-net-memory-leak-2/
[13] Visual Studio 确诊专用工具: https://docs.microsoft.com/en-us/visualstudio/profiling/memory-usage?view=vs-2022
[14] 到根的最短路径算法: https://www.jetbrains.com/help/dotmemory/Shortest_Paths_to_Roots.html
[15] 内存分析器来表明分派: https://www.jetbrains.com/help/dotmemory/Analyze_Memory_Allocation.html#types
[16] 产生这种现象的缘故: https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/
[17] 应用内存分析器调节泄露: https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/#profiler
[18] Sergey Tepliakov: https://devblogs.microsoft.com/premier-developer/understanding-different-gc-modes-with-concurrency-visualizer/
[19] GC 工作压力: https://michaelscodingspot.com/avoid-gc-pressure/
[20] .NET 中的目前缓存文件完成: https://michaelscodingspot.com/cache-implementations-in-csharp-net/
[21] Redis: https://redis.io/
[22] PerfView: https://github.com/microsoft/perfview
[23] dotTrace: https://www.jetbrains.com/profiler/
[24] ANTS perf profiler: https://www.red-gate.com/products/dotnet-development/ants-performance-profiler/
[25] 应用 Visual Studio 。: https://docs.microsoft.com/en-us/visualstudio/profiling/beginners-guide-to-performance-profiling?view=vs-2022
[26] Azure Application Insights profiler: https://docs.microsoft.com/en-us/azure/azure-monitor/app/profiler-overview
[27] RayGun: https://raygun.com/for/dotnet-performance-monitoring
[28] 的轻量代理商。: https://blog.jetbrains.com/dotnet/2012/09/10/dottrace-remote-profiling/
[29] 应用 dotnet-trace 命令行工具纪录快照更新: https://michaelscodingspot.com/dotnet-trace/
[30] 性能计数: https://michaelscodingspot.com/performance-counters/
[31] 方法解决它: https://michaelscodingspot.com/avoid-gc-pressure/
[32] (: https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-6.0
[33] GC 工作平台方式: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/workstation-server-gc
[34] 在做到某一 CPU 峰值时全自动终止搜集: https://www.drware.com/perfview-command-for-capturing-automated-high-cpu-dumps/
[35] Practical Debugging for .NET 开发人员: https://practicaldebugging.net/