本文章是由機器翻譯。

Visual Studio2015

分析性能的同時,在Visual Studio2015年調試

Charles Willis
Dan Taylor

許多開發商花了絕大多數時間獲取應用程式才能正常發揮作用。更少的時間裡專注于應用程式的性能。雖然有了很長一段時間分析工具在Visual Studio,他們是單獨的一組學習工具。許多開發人員沒有花時間去學習和使用它們的時候會出現性能問題。

這篇文章將介紹在Visual Studio2015年新的診斷工具調試器視窗。 它還將描述如何使用它來分析性能作為定期調試工作流的一部分。我會首先提供調試器的特性和功能的概述,然後對一個深潛演練。我會告訴你如何使用 PerfTips 來時間剖面的中斷點和步驟之間的代碼、 如何使用診斷工具視窗來監視 CPU 和記憶體,以及如何拍攝快照來鑽深進記憶體增長和洩漏。

這篇文章中的功能都可用於調試最託管和本機專案。微軟不斷地添加更多的專案類型的支援、 調試配置。有關當前受支援的功能的最新資訊,請查閱診斷工具視窗在博客的帖子 aka.ms/diagtoolswindow。在這個問題上單獨的一條將解釋如何在診斷工具視窗內使用 IntelliTrace (見"使用 IntelliTrace 對診斷問題更快,") 來快速確定您的代碼中的 bug 的根源。

在調試時的性能

而不是運行一個完整的分析工具,你可能會採取一個或多個以下的步驟:

  1. 將代碼插入到應用程式 (例如 System.Diagnostics.Stop­手錶) 來衡量各種點,根據需要來縮小熱路徑反覆運算添加碼錶之間運行所需的多長時間。
  2. 逐句通過代碼,看看如果任何特別是單一步驟"感覺慢"。
  3. 所有 ("暫停") 按鈕在隨機點去感受如何遠的執行已取得進展的突破。在某些圈子裡這指作為"窮人的採樣"。
  4. 過度優化代碼沒有測量性能,有時通過在整個代碼庫應用一套性能最佳做法。

這些做法通常是不准確的不是用時間或兩者都好。這就是為什麼現在有性能工具在調試器中。他們將説明您瞭解您的應用程式的性能,在正常調試過程中。

診斷工具視窗 您會注意到當Visual Studio2015年中的調試代碼的新的診斷工具視窗中將會出現,如中所示的主要差別在於 圖 1。這些診斷工具在兩個相互補充的方式呈現資訊。他們將圖形添加到時間軸中視窗的上半,並提供詳細的資訊選項卡中的底部。

Visual Studio2015年新的診斷工具視窗
圖 1Visual Studio2015年新的診斷工具視窗

在Visual Studio2015年,您會看到在診斷工具視窗中的三個工具:調試器 (包括 IntelliTrace)、 記憶體和 CPU 使用率。您可以啟用或禁用的 CPU 使用率和記憶體使用的工具,通過點擊選擇工具下拉清單。調試器工具已表明打破事件、 輸出事件和 IntelliTrace 事件的三個軌道。

打破歷史上重大事件和 PerfTips 打破事件讓你看到每一節代碼花多長時間運行。矩形代表從應用程式開始或恢復執行,直至調試器作出它暫停時的持續時間 (見圖 2)。

斷裂事件和 PerfTips
圖 2 斷裂事件和 PerfTips

矩形的起點指示你從哪裡開始通過繼續步進 Shift + F11 F11 F10) 或運行到游標處 (Ctrl + F10) 命令 (F5),運行應用程式。結束了該矩形指示因為它命中了中斷點,完成一個步驟或因為您使用了打破所有應用程式停止的位置。

最新的中斷事件的持續時間也顯示在調試器中的當前行末尾處的代碼。這被稱為 PerfTips。它允許您監視性能沒有考慮你的眼睛離開你的代碼。

在關係圖下方的細節表中,也可以看到歷史和打破事件和 PerfTips 表格格式的持續時間。如果你有 IntelliTrace,附加事件將顯示在表中。您還可以使用篩選器顯示只有調試器來查看只有打破事件的歷史記錄。

CPU 和記憶體分析時間軸自動選擇時間範圍內,如您設置中斷點並單步。當遇到中斷點時,目前時間範圍被重置,只將最新的中斷事件顯示。所選內容可以擴大到包括最新的中斷事件。通過點擊一個打破事件矩形或通過按一下並拖動時間表上,您可以覆蓋自動時間範圍選擇。

時間範圍選擇允許您關聯範圍上的 CPU 使用率和記憶體使用方式圖,以便您可以理解代碼的特定部分的 CPU 和記憶體的特點。圖繼續更新在該應用程式運行時,讓你幫我照看對 CPU 和記憶體作為您與您的應用程式進行交互。您可以切換到記憶體選項卡,拍攝快照並查看記憶體使用方式的詳細分項數位。

IntelliTrace 性能洞察力 IntelliTrace (Visual Studio社區版中不可用) 讓你獲得更多的洞察性能調試託管的代碼時。IntelliTrace 向調試器事件時間表中添加兩個軌道:輸出跟蹤和 IntelliTrace 軌道。這些事件包括資訊顯示在輸出視窗中,再加上額外的事件收集的 IntelliTrace,如異常,ADO.NET,等等。在這些軌道發生的事件也是調試器事件表所示。

你可以涉及的 IntelliTrace 事件穗狀花序的 CPU 使用率和記憶體使用方式圖。時間戳記顯示你多久各種行動在您的應用程式。例如,您可以在您的代碼中添加 Debug.WriteLine 語句,使用時間戳上輸出事件查看從一個語句到下一個運行所需要的多長時間。

提高性能和記憶體

現在,您已經看到該視窗的功能,我們將深入探討實際用途的工具。在本節中,我們步行通過求解一組稱為照片濾鏡的樣例應用程式中的性能問題。這個應用程式將從雲端下載圖片和從使用者的本地圖片庫載入圖片,這樣他可以查看它們,並應用圖像篩選器。

如果你想要跟著,下載原始程式碼從 aka.ms/diagtoolswndsample。因為性能是不同的不同的機器上,你會發現不同的數位。它甚至會有所不同從運行。

慢啟動應用程式當您開始調試照片濾鏡應用程式時,你會發現它需要很長的時間,要啟動應用程式和載入的圖片。這是一個明顯的問題。

當您在調試應用程式的功能性問題時,你往往形成一個假設並開始調試在此基礎。在這種情況下,你能推測圖片是緩慢載入,並尋找一個好的地方設置一個中斷點,並考驗一下這個假說。LoadImages 方法是一個偉大的地方,做到這一點。

開始和結束時的 LoadImages 函數設置中斷點 (如圖所示的代碼中圖 3) 和開始調試 (F5)。當代碼命中第一個中斷點處時,按繼續 (F5) 再次跑到第二個中斷點。現在有兩個斷裂事件在時間軸中調試器事件。

LoadImages 方法

LoadImages 方法
圖 3 LoadImages 方法

第一步顯示應用程式運行僅 274 毫秒之前命中第一個中斷點。第二部分演示了 10,476 女士要打第二個中斷點之前在 LoadImages 函數中運行此代碼。您還可以看到經過時間 PerfTip 在代碼中顯示相同的值。所以你已經縮小到 LoadImages 函數問題。

若要獲取更多的資訊和每一行需要多久的時間,重新開機調試所以你再打第一個中斷點。這一次,逐句通過每一行代碼中方法以查看哪些行正在最長的時間。從 PerfTips 和調試中斷事件的持續時間,你可以看到 GetImagesFromCloud 花 7,290 女士、 LoadImagesFromDisk 需要 736 女士、LINQ查詢所需 1,322 女士和其餘在小於 50 毫秒內完成。

對於所有行時機所示圖 4。行號顯示代表大課間活動,末尾處的線,所以線 52 意味著多長時間它接管了步線 51。 現在鑽進一步入 GetImagesFromCloud 方法。

調試器事件表的每個步驟顯示經過的時間
圖 4 調試器事件表的每個步驟顯示經過的時間

GetImagesFromCloud 方法執行兩個獨立的邏輯操作,如中所示圖 5。它從伺服器和每張圖片的縮略圖中同步下載圖片的清單 (一次一個)。您可以通過取消您現有的中斷點並將放在以下各行的新的時間這兩個操作:

line 63: HttpClient client = new HttpClient();
line 73: foreach (var image in pictureList)
line 79: }

GetImagesFromCloud 方法改進了代碼
圖 5 改進了代碼 (下圖) 與 GetImagesFromCloud 方法 (頂部)

重新開機調試進程並等待,直到該應用程式命中第一個中斷點處。然後讓應用程式能夠運行 (通過按 f5 鍵以繼續) 向第二個中斷點。這允許應用程式從雲中檢索圖片的清單。然後讓應用程式運行到第二個中斷點來衡量從雲端下載縮略圖。PerfTips 和打破事件告訴您花了 565 女士得到的圖片清單和 6,426 的 ms 下載縮略圖。性能瓶頸是在你下載的縮略圖。

當你看著 CPU 使用率圖 (所示圖 6),該方法檢索的圖像清單,你可以看到它是相對較高。圖形是相當平坦時表明這一過程花了很長一段時間,等待網路 I/O 的縮略圖下載。

CPU 使用率圖指示延遲網路輸入/輸出
圖 6 CPU 使用率圖指示延遲網路輸入/輸出

為了儘量減少等待用戶端和伺服器之間的往返時間,立即開始下載縮略圖的所有操作,等待他們通過等待完成.NET System.Tasks 來完成。 取代第 73 至 79 行 (從代碼中圖 5) 用下面的代碼:

// Download thumbnails
var downloadTasks = new List<Task>();
foreach (var image in pictureList)
{
  string fileName = image.Thumbnail;
  string imageUrl = ServerUrl + "/Images/" + fileName;
  downloadTasks.Add(DownloadImageAsync(new Uri(imageUrl), folder, fileName));
}
await Task.WhenAll(downloadTasks);

當你的時候這個新的版本時,你可以看到需要只有 2,424 女士來運行。這是關於四秒的改進。

調試記憶體增長和洩漏如果你看看記憶體使用方式圖診斷慢啟動時,您可能已經注意到記憶體使用量的急劇增加為啟動該應用程式。縮略圖的清單是虛擬化的清單,而只有一個完全尺寸的圖像顯示在一段時間。使用虛擬化的清單的優點之一是它僅載入內容顯示在螢幕上,所以你不會期望很多縮略圖在記憶體中一次。

要到這一問題的根本原因,你必須找到在代碼中記憶體增長出現。然後,拍取快照之前和之後的增長。比較這些快照,你會發現最有助於生長在記憶體中的物件類型。

記憶體使用方式圖顯示了應用程式如何使用記憶體的高級視圖。還有計數器命名專用位元組數為您的應用程式的性能。專用位元組數是記憶體的衡量的分配給進程總量。這還不包括與其他進程共用的記憶體。它包括託管的堆、 本機堆、 執行緒堆疊和其他記憶體 (如載入的.DLL 檔案的私人部門)。

當開發一個新的應用程式或診斷問題與現有的一個,意外的增長的記憶體使用方式圖上會經常是你已經不表現如預期的代碼的第一個跡象。看圖,您可以使用調試器功能如中斷點和單一步驟來縮小感興趣的代碼路徑。你可以從行號和持續時間顯示在調試器事件選項卡重新確定圖 4負責意外增長線是線 52,LoadImagesFromDisk 方法調用。拍攝快照,通常會針對意外的記憶體使用方式的下一步。在記憶體選項卡,按一下拍攝快照按鈕生成堆的快照。在中斷點處或應用程式正在運行時,您可以拍攝快照。

如果您知道哪一行代碼導致尖峰記憶體使用量,然後你有一個想法在哪裡採取第一個快照。LoadImagesFromDisk 方法上設置中斷點和拍攝快照,當您的代碼到達該中斷點。此快照作為基線。

接下來,單一步驟的 LoadImagesFromDisk 方法,並生成另一個快照。現在,通過比較快照,你將能夠看到哪些託管的類型已添加到你跨過為函式呼叫的結果堆。圖再一次顯示了記憶體利用率穗正在調查 (如中所示圖 7)。您還可以看到通過滑鼠懸停圖形記憶體 47.4 MB。它是一個好的主意來心理記數百萬位元組,所以您可以稍後驗證您修復了有意義的影響。

有是記憶體使用量明顯激增
圖 7 有是記憶體使用量明顯激增

詳細資訊視圖顯示每個快照的簡要的概述。概述包括快照的順序號,運行的時間 (以秒為單位) 時拍攝快照,堆的大小和數量的活堆上的物件。後續快照也會顯示變化的大小和物件計數從以前的快照。

拍攝快照的過程列舉了只有那些仍然生活在堆上的物件。也就是說,如果物件是符合垃圾回收條件,不會包括在快照中。這種方式,你不需要擔心當集合最後跑了。每個快照中的資料是,仿佛剛剛發生垃圾回收。

顯示快照概述在堆的大小將低於專用位元組記憶體使用方式圖表中顯示。專用位元組數概要顯示所有類型的由您的進程分配的記憶體,而快照顯示的大小所有活物件在託管堆上。如果你看到在記憶體使用方式圖中,大量增加,但增長託管堆中的並不占多數的它,生長在記憶體中其他地方發生。

從快照概述中,您可以打開堆視圖並調查按類型堆的內容。按一下第二個快照,在新選項卡中打開堆視圖的物件 (差異) 列中 diff 連結。按一下該連結將排序堆視圖中的類型由自以前的快照創建的新物件的數目。這讓你感興趣頂部的表的類型。

堆視圖快照 (見圖 8) 有兩個主要部分:頂部窗格和下部窗格中引用圖中的物件類型表。物件類型表顯示的名稱、 數量和大小的每個物件類型時拍攝快照。

中 Diff 模式的堆視圖快照
圖 8 中 Diff 模式的堆視圖快照

幾個在堆檢視類型是從框架。如果你有僅我的代碼啟用 (預設值),這些都是在您的代碼中參考型別或由您的代碼類型引用的類型。使用此視圖,您可以識別一個類型從我們靠近頂部的 table—PhotoFilter.ImageItem 的代碼。

圖 8,你可以看到計數 Diff 列顯示自以前的快照創建的 137 新映射物件。頂五個新物件類型都有相同數量的新的物件,所以這些可能相關。

讓我們看看第二個窗格,參考圖。如果你期望要清理垃圾回收器的類型,但它仍顯示在類型表中,根的路徑可以説明您跟蹤下什麼持有該引用。到根的路徑是參考圖中這兩種視圖之一。到根的路徑是自底向上樹顯示完整的圖形類型生根您所選的類型。如果另一個應用程式物件保存了一個引用,植根的物件。不必要的根的物件往往是在託管代碼中記憶體洩露的原因。

引用的類型,另一個視圖,則相反。為在物件類型表中,選擇此視圖顯示其他類型的類型引用了您選定的類型。此資訊可以有助於確定為什麼在更多的記憶體比預期堅持所選類型的物件。這是有用的現狀調查,因為類型可能會使用更多的記憶體比預期,但他們並不比它們的用處。

在物件類型表中選擇 PhotoFilter.ImageItem 行。參考圖將更新以顯示關係圖的映射。在參考型別視圖中,您可以看到映射物件保留共 280 的字串物件和 140 每個框架的三種類型:StorageFile、 StorageItemThumbnail 和 BitmapImage。

總大小使它看起來好像貢獻由映射物件保留的記憶體的增加最大字串物件。專注于總大小 Diff 列使很有意義,但數目不會導致的根本原因。一些框架類型,如 BitmapImage,只有極少量的舉行在託管堆上的記憶體總量。BitmapImage 實例的數量是一個更令人信服的線索。記得在照片濾鏡的縮略圖的清單虛擬的所以它應該載入這些映射上的需求,並使其可作為垃圾回收,當它做。然而,看起來好像提前載入所有縮略圖。結合你現在瞭解什麼 BitmapImage 物件被冰山,繼續專注于那些進行調查。

PhotoFilter.ImageItem 在參考圖中,右擊並選擇轉到定義映射在編輯器中打開原始檔案。映射定義的成員欄位,m_photo,即 BitmapImage,如中所示圖 9

代碼引用 m_photo 的成員欄位
圖 9 代碼引用 m_photo 的成員欄位

第一個代碼路徑引用 m_photo 是屬性的 get 方法的照片,它是屬性的資料繫結到 ListView 在 UI 中的縮略圖。它看起來像 BitmapImage 正在載入 (和因此解碼在本機堆上) 上的需求。

引用 m_photo 的第二個代碼路徑是 LoadImageFromDisk 的功能。這個專案是對應用程式的啟動路徑。應用程式啟動時,它獲取呼籲被顯示,每個圖像。這有效地預載入 BitmapImage 的所有物件。這種行為不利於虛擬清單視圖中,作為已分配的所有記憶體,無論清單視圖中顯示圖像的縮略圖。該預載入演算法不能很好地擴展。更多的圖片,你有在你的圖片庫中,啟動記憶體成本也就越高。按需載入 BitmapImage 物件是更具可擴充性的解決方案。

後停止調試器,請注釋掉線 81 和 82 在 LoadImageFromDisk 載入 BitmapImage 實例。為驗證您已經固定記憶體性能問題而不會破壞應用程式的功能,然後重新運行相同的實驗。

按 F5,你就會看到上圖總記憶體使用量是現在只有 26.7 MB (見圖 10)。以另一組的快照之前、 之後調用 LoadImagesFromDisk,然後比較他們。你會看到仍然是 137 的映射物件,但沒有 BitmapImages (見圖 11)。BitmapImages 將載入需求上,一旦你讓應用程式繼續啟動。

記憶體圖在固定的引用問題
圖 10 記憶體圖在固定的引用問題

參考圖後固定記憶體的問題
圖 11 參考圖後固定記憶體的問題

正如前面提到的此調試器集成的工具還支援拍照的本機堆或託管和本機堆同時。堆你設定檔為基礎上的調試器,您正在使用:

  • 僅託管調試器只需託管堆的快照。
  • 僅限本機調試器 (本機專案的預設值) 只需要本機堆快照。
  • 混合模式調試器需要的託管和本機堆快照。

您可以調整此設置對您的專案屬性的調試頁。

當不啟用調試運行工具

它是重要的是提及需要額外開銷介紹了當您測量與調試器的性能。主類的開銷來自您通常運行的應用程式的調試版本的事實。您發佈到使用者的應用程式將發佈版本。

在調試版本中,編譯器保持了盡可能接近原始的原始程式碼作為可能的結構和行為的可執行檔。正如您期望在調試時,一切應該工作。另一方面,發佈版本試圖優化代碼的性能降低的調試體驗的方式。一些例子包括襯砌中的函式呼叫和常數變數,移除未使用的變數和代碼路徑和存儲變數資訊可能無法讀取由調試器的方式。

所有這一切意味著 CPU 密集型代碼可以有時運行速度顯著變慢在調試版本中。非 CPU 密集型的操作,如磁片 I/O 和網路調用將採取同樣多的時間。通常不是記憶行為,意味著洩漏的記憶體會洩露和低效的記憶體的使用仍將顯示作為大量增加這兩種情況差別很大。

它附加到目標應用程式時,將由調試器添加其他開銷的類來考慮。調試器截獲模組載入和異常的事件。它也提供其他所需的工作讓你設置中斷點和步驟。Visual Studio會盡力篩選這種類型的開銷從性能的工具,但仍有少量的開銷。

如果你看到一個應用程式的發佈版本中的一個問題,它將幾乎總是複製在調試版本中,但不是一定在附近的其他方式。為此,調試器集成工具旨在説明您在開發過程中主動地發現性能問題。如果您在調試版本中發現的問題,你可以翻到發佈版本,看如果這一問題影響以及發佈版本。但是,您可能決定往前走並在調試版本中解決該問題,如果你決定這是預防性能良好的工作 (也就是說,修復問題降低以後遇到性能問題的機會),如果您確定問題是非 CPU 密集型 (磁片或網路 I/O),或如果您想要加快調試版本,所以在開發過程中,您的應用程式是迅捷。

報告性能問題時在發佈版本中,您想要確保您可以複製和驗證你已經解決了問題。最好的辦法做到這一點是翻轉您為發佈模式的生成與所報告的問題相符的環境中運行的工具不使用調試器。

如果你想測量業務持續時間,調試器-­集成的工具將只能精確到內幾十毫秒量較少的系統開銷。如果你需要更高一級,運行不用調試器工具是準確性的一個更好的選擇。

接近尾聲了

你可以通過下載Visual Studio2015 RC 嘗試Visual Studio2015年新的診斷工具調試器視窗。使用這些新的集成調試工具可以説明您提高性能,您在調試您的應用程式。


Dan TaylorVisual Studio診斷團隊的專案經理,過去兩年裡一直在分析和診斷工具。 之前加入Visual Studio診斷團隊,泰勒是.NET 框架團隊的專案經理,並貢獻到.NET 框架和 CLR 的諸多性能改進。

Charles Willis 加入了微軟去年以來一直在Visual Studio診斷團隊的專案經理。

感謝以下的微軟技術專家對本文的審閱:安德魯 · 霍爾,Daniel 蛾和世彼得羅普洛斯
世彼得羅普洛斯是Visual Studio團隊的高級專案經理。得到他的主人在物件導向軟體工程後,他擔任了在英國的一名 IT 顧問。搬到美國後,他加入了Visual Studio的診斷工具團隊和他現在是用於 IntelliTrace 的程式管理器

Daniel 蛾是微軟的Visual Studio小組的首席專案經理。