最近在爲 Newbe.Claptrap 作性能升級,所以將過程當中使用到的 dotTrace 軟件的基礎用法介紹給各位開發者。php
Newbe.Claptrap 是一個用於輕鬆應對併發問題的分佈式開發框架。若是您是首次閱讀本系列文章。建議能夠先從本文末尾的入門文章開始瞭解。git
開篇摘要
dotTrace 是 Jetbrains 公司爲 .net 應用提供的一款 profile 軟件。有助於對於軟件中的耗時函數和內存問題進行診斷分析。github
本篇,咱們將使用 Jetbrains 公司的 dotTrace 軟件對一些已知的性能問題進行分析。從而使讀者可以掌握使用該軟件的基本技能。面試
過程當中咱們將搭配一些經典的面試問題進行演示,逐步解釋該軟件的使用。docker
這次示例使用的是 Rider 做爲主要演示的 IDE。開發者也可使用 VS + Resharper 作出相同的效果。數據庫
如何獲取 dotTrace
dotTrace 是付費軟件。目前只要購買 dotUltimate 及以上的許可證即可以直接使用該軟件。編程
固然,該軟件也包含試用版本,能夠免費開啓 7 天的試用時間。Jetbrains 的 IDE 購買滿一年以上便可獲取一個當前最新的永久使用版本。數組
或者也能夠直接購買 Jetbrains 全家桶許可證,一次性所有帶走。服務器
經典場景再現
接下來,咱們經過一些經典的面試問題,來體驗一下如何使用 dotTrace。併發
什麼時候要使用 StringBuilder
這是多麼經典的面試問題。可以看到這篇文章的朋友,我相信各位都知道 StringBuilder 可以減小 string 直接拼接的碎片,減小內存壓力這個道理。
咱們這是真的嗎?會不會只是面試官想要刁難我,欺負我信息不對稱呢?
沒有關係,接下來,讓咱們使用 dotTrace 來具體的結合代碼來分析一波。看看使用 StringBuilder 究竟有沒有減低內存分配的壓力。
首先,咱們建立一個單元測試項目,並添加如下這樣一個測試類:
using System.Linq; using System.Text; using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X01StringBuilderTest { [Test] public void UsingString() { var source = Enumerable.Range(0, 10) .Select(x => x.ToString()) .ToArray(); var re = string.Empty; for (int i = 0; i < 10_000; i++) { re += source[i % 10]; } } [Test] public void UsingStringBuilder() { var source = Enumerable.Range(0, 10) .Select(x => x.ToString()) .ToArray(); var sb = new StringBuilder(); for (var i = 0; i < 10_000; i++) { sb.Append(source[i % 10]); } var _ = sb.ToString(); } } } |
而後,以下圖所示,咱們將 Rider 中的 profile 模式設置爲 Timeline 。
TimeLine 是多種模式中的一種,相較而言,該模式能夠更全面的瞭解各個線程的工做狀況,包括有內存分配、IO 處理、鎖、反射等等多維度數據。這將會做爲本示例主要使用的一種模式。
接着,以下圖所示,經過單元測試左側的小圖標啓動對應測試的 profile。
啓動 profile 以後,等待一段時間以後,便會出現最新生成的 timeline 報告。查看報告的位置以下所示:
右鍵選擇對應的報告,選擇」Open in External Viewer」,即可以使用 dotTrace 打開生成好的報告。
那麼首先,讓我打開第一個報告,查看 UsingString 方法生成的報告。
以下圖所示,選擇 .Net Memory Allocations 以查看該測試運行過程當中分配的內存數額。
根據上圖咱們能夠得出如下結論:
在這測試中,有 102M 的內存被分配給 String 。注意,在 dotTrace 中顯示的分配是指整個運行過程當中所有分配的內存。即便後續被回收,該數值也不會減小。
內存的分配只要在 CLR Worker 線程進行。而且很是的密集。
Tip:Timeline 所顯示的運行時間比正常運行測試的時間更長,由於在 profile 過程當中須要對數據進行記錄會有額外的消耗。
所以,咱們就得出了第一個結論:使用 string 進行直接拼接,確實會消耗更多的內存分配。
接着,咱們繼續按照上面的步驟,查看一下 UsingStringBuilder 方法的報告,以下所示:
根據上圖,咱們能夠得出第二個結論:使用 StringBuilder 能夠明顯的減小相較於 string 直接拼接所消耗的內存。
固然,咱們獲得的最終的結論實際上是:看來面試官不是糊弄人。
class 和 struct 對內存有什麼影響
class 和 struct 的區別有不少,面試題常客了。其中,二者在內存方面就存在區別。
那麼咱們經過一個測試來看看區別。
using System; using System.Collections.Generic; using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X02ClassAndStruct { [Test] public void UsingClass() { Console.WriteLine($"memory in bytes before execution: {GC.GetGCMemoryInfo().TotalAvailableMemoryBytes}"); const int count = 1_000_000; var list = new List<Student>(count); for (var i = 0; i < count; i++) { list.Add(new Student { Level = int.MinValue }); } list.Clear(); var gcMemoryInfo = GC.GetGCMemoryInfo(); Console.WriteLine($"heap size: {gcMemoryInfo.HeapSizeBytes}"); Console.WriteLine($"memory in bytes end of execution: {gcMemoryInfo.TotalAvailableMemoryBytes}"); } [Test] public void UsingStruct() { Console.WriteLine($"memory in bytes before execution: {GC.GetGCMemoryInfo().TotalAvailableMemoryBytes}"); const int count = 1_000_000; var list = new List<Yueluo>(count); for (var i = 0; i < count; i++) { list.Add(new Yueluo { Level = int.MinValue }); } list.Clear(); var gcMemoryInfo = GC.GetGCMemoryInfo(); Console.WriteLine($"heap size: {gcMemoryInfo.HeapSizeBytes}"); Console.WriteLine($"memory in bytes end of execution: {gcMemoryInfo.TotalAvailableMemoryBytes}"); } public class Student { public int Level { get; set; } } public struct Yueluo { public int Level { get; set; } } } } |
代碼要點:
兩個測試,分別建立 1,000,000 個 class 和 struct 加入到 List 中。
運行測試以後,在測試的末尾輸出當前堆空間的大小。
按照上一節提供的基礎步驟,咱們對比兩個方法生成的報告。
UsingClass
UsingStruct
對比兩個報告,能夠得出如下這些結論:
Timeline 報告中的內存分配,只包含分配在堆上的內存狀況。
struct 不須要分配在堆上,可是,數組是引用對象,須要分配在堆上。
List 自增的過程本質是擴張數組的特性在報告中也獲得了體現。
另外,沒有展現在報告上,而展現在測試打印文本中能夠看到,UsingStruct 運行以後的堆大小也證明了 struct 不會被分配在堆上。
裝箱和拆箱
經典面試題 X3,來,上代碼,上報告!
using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X03Boxing { [Test] public void Boxing() { for (int i = 0; i < 1_000_000; i++) { UseObject(i); } } [Test] public void NoBoxing() { for (int i = 0; i < 1_000_000; i++) { UseInt(i); } } public static void UseInt(int age) { // nothing } public static void UseObject(object obj) { // nothing } } } |
Boxing, 發生裝箱拆箱
NoBoxing,未發生裝箱拆箱
對比兩個報告,能夠得出如下這些結論:
沒有買賣就沒有殺害,沒有裝拆就沒有分配消耗。
Thread.Sleep 和 Task.Delay 有什麼區別
經典面試題 X4,來,上代碼,上報告!
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X04SleepTest { [Test] public Task TaskDelay() { return Task.Delay(TimeSpan.FromSeconds(3)); } [Test] public Task ThreadSleep() { return Task.Run(() => { Thread.Sleep(TimeSpan.FromSeconds(3)); }); } } } |
ThreadSleep
TaskDelay
對比兩個報告,能夠得出如下這些結論:
在 dotTrace 中 Thread.Sleep 會被單獨標記,由於這是一種性能不不佳的作法,容易形成線程飢餓。
Thread.Sleep 比起 Task.Delay 會多出一個線程處於 Sleep 狀態
阻塞大量的 Task 真的會致使應用一動不動嗎
有了上一步的結論,筆者產生了一個大膽的想法。咱們都知道線程的有限的,那若是啓動很是多的 Thread.Sleep 或者 Task.Delay 會如何呢?
來,代碼:
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X04SleepTest { [Test] public Task RunThreadSleep() { return Task.WhenAny(GetTasks(50)); IEnumerable<Task> GetTasks(int count) { for (int i = 0; i < count; i++) { var i1 = i; yield return Task.Run(() => { Console.WriteLine($"Task {i1}"); Thread.Sleep(int.MaxValue); }); } yield return Task.Run(() => { Console.WriteLine("yueluo is the only one dalao"); }); } } [Test] public Task RunTaskDelay() { return Task.WhenAny(GetTasks(50)); IEnumerable<Task> GetTasks(int count) { for (int i = 0; i < count; i++) { var i1 = i; yield return Task.Run(() => { Console.WriteLine($"Task {i1}"); return Task.Delay(TimeSpan.FromSeconds(int.MaxValue)); }); } yield return Task.Run(() => { Console.WriteLine("yueluo is the only one dalao"); }); } } } } |
這裏就不貼報告了,讀者能夠試一下這個測試,也能夠將報告的內容寫在本文的評論中參與討論~
反射調用和表達式樹編譯調用
有時,咱們須要動態調用一個方法。最廣爲人知的方式就是使用反射。
可是,這也是廣爲人知的耗時相對較高的方式。
這裏,筆者提供一種使用表達式樹建立委託來取代反射提升效率的思路。
那麼,究竟有沒有減小時間消耗呢?好報告,本身會說話。
using System; using System.Diagnostics; using System.Linq.Expressions; using NUnit.Framework; namespace Newbe.DotTrace.Tests { public class X05ReflectionTest { [Test] public void RunReflection() { var methodInfo = GetType().GetMethod(nameof(MoYue)); Debug.Assert(methodInfo != null, nameof(methodInfo) + " != null"); for (int i = 0; i < 1_000_000; i++) { methodInfo.Invoke(null, null); } Console.WriteLine(_count); } [Test] public void RunExpression() { var methodInfo = GetType().GetMethod(nameof(MoYue)); Debug.Assert(methodInfo != null, nameof(methodInfo) + " != null"); var methodCallExpression = Expression.Call(methodInfo); var lambdaExpression = Expression.Lambda<Action>(methodCallExpression); var func = lambdaExpression.Compile(); for (int i = 0; i < 1_000_000; i++) { func.Invoke(); } Console.WriteLine(_count); } private static int _count = 0; public static void MoYue() { _count++; } } } |
RunReflection,直接使用反射調用。
RunExpression,使用表達式樹編譯一個委託。
本篇小結
使用 dotTrace 能夠查看方法的內存和時間消耗。本篇所演示的內容只是其中很小的部分。開發者們能夠嘗試上手,大有裨益。
本篇內容中的示例代碼,都可以在如下連接倉庫中找到:
https://github.com/newbe36524/Newbe.Demo
https://gitee.com/yks/Newbe.Demo
最後可是最重要!
若是讀者對該內容感興趣,歡迎轉發、評論、收藏文章以及項目。
最近做者正在構建以反應式
、Actor模式
和事件溯源
爲理論基礎的一套服務端開發框架。但願爲開發者提供可以便於開發出 「分佈式」、「可水平擴展」、「可測試性高」 的應用系統 ——Newbe.Claptrap
本篇文章是該框架的一篇技術選文,屬於技術構成的一部分。
聯繫方式:
Github Issue
Gitee Issue
公開郵箱 newbe-claptrap@googlegroups.com (發送到該郵箱的內容將被公開)
Gitter
QQ 羣 610394020
您還能夠查閱本系列的其餘選文:
理論入門篇
Newbe.Claptrap - 一套以 「事件溯源」 和 「Actor 模式」 做爲基本理論的服務端開發框架
術語介紹篇
Actor 模式
事件溯源(Event Sourcing)
Claptrap
Minion
事件 (Event)
狀態 (State)
狀態快照 (State Snapshot)
Claptrap 設計圖 (Claptrap Design)
Claptrap 工廠 (Claptrap Factory)
Claptrap Identity
Claptrap Box
Claptrap 生命週期(Claptrap Lifetime Scope)
序列化(Serialization)
實現入門篇
Newbe.Claptrap 框架入門,第一步 —— 建立項目,實現簡易購物車
Newbe.Claptrap 框架入門,第二步 —— 簡單業務,清空購物車
Newbe.Claptrap 框架入門,第三步 —— 定義 Claptrap,管理商品庫存
Newbe.Claptrap 框架入門,第四步 —— 利用 Minion,商品下單
樣例實踐篇
構建一個簡易的火車票售票系統,Newbe.Claptrap 框架用例,第一步 —— 業務分析
在線體驗火車票售票系統
其餘番外篇
談反應式編程在服務端中的應用,數據庫操做優化,從 20 秒到 0.5 秒
談反應式編程在服務端中的應用,數據庫操做優化,提速 Upsert
十萬同時在線用戶,須要多少內存?——Newbe.Claptrap 框架水平擴展實驗
docker-mcr 助您全速下載 dotnet 鏡像
十多位全球技術專家,爲你獻上近十個小時的.Net 微服務介紹
年輕的樵夫喲,你掉的是這個免費 8 核 4G 公網服務器,仍是這個隨時可用的 Docker 實驗平臺?
如何使用 dotTrace 來診斷 netcore 應用的性能問題
GitHub 項目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 項目地址:https://gitee.com/yks/Newbe.Claptrap
您當前查看的是先行發佈於 www.newbe.pro 上的博客文章,實際開發文檔隨版本而迭代。若要查看最新的開發文檔,須要移步 claptrap.newbe.pro。