優化程式代碼可降低計算時間和成本。 此案例研究示範如何使用 Visual Studio 分析工具來識別並修正範例 .NET 應用程式中的效能問題。 如果您想要比較分析工具,請參閱 我應該選擇哪一個工具?
本指南涵蓋:
- 如何使用 Visual Studio 分析工具來分析及改善效能。
- 優化CPU使用量、記憶體配置和資料庫互動的實際策略。
套用這些技術,讓您的應用程式更有效率。
優化案例研究
範例 .NET 應用程式會針對使用 Entity Framework 的部落格和文章 SQLite 資料庫執行查詢。 它會執行許多查詢,模擬真實世界的數據擷取案例。 應用程式是以 Entity Framework 使用者入門範例為基礎,但會使用較大的數據集。
主要效能問題包括:
- 高 CPU 使用量:沒有效率的計算或處理工作會增加 CPU 耗用量和成本。
- 記憶體配置效率不佳:記憶體管理不佳會導致過多的垃圾收集並降低效能。
- 資料庫額外負荷:沒有效率的查詢和過多的資料庫呼叫會降低效能。
此案例研究會使用 Visual Studio 分析工具來找出並解決這些問題,目的是讓應用程式更有效率且符合成本效益。
挑戰
修正這些效能問題牽涉到數個挑戰:
- 診斷瓶頸:識別高 CPU、記憶體或資料庫額外負荷的根本原因,需要有效使用分析工具和正確解譯結果。
- 知識和資源限制:分析與優化需要特定的技能和經驗,這不一定一定可供使用。
結合分析工具、技術知識和仔細測試的策略方法,對於克服這些挑戰至關重要。
策略
以下是此案例研究中方法的高階檢視:
- 使用 Visual Studio 的 CPU 使用量工具,從 CPU 使用量追蹤開始。 Visual Studio 的 CPU 使用量工具是效能調查的良好起點。
- 收集記憶體和資料庫分析的額外追蹤。
- 使用 .NET 物件配置工具 進行記憶體深入解析。
- 使用 資料庫工具來 檢查 SQL 查詢和計時。
資料收集需要下列工作:
- 將應用程式設定為發行版本。
- 在 [效能分析工具] 中選取 [CPU 使用量] 工具 (Alt+F2)。
- 在效能分析器中,啟動應用程式並收集追蹤數據。
檢查高 CPU 使用量的區域
使用 CPU 使用量工具收集追蹤並將其載入 Visual Studio 之後,我們會先檢查初始 .diagsession 報表頁面,其中顯示摘要數據。 使用報表中的 開啟詳細數據 連結。
在報告詳細檢視中,開啟 [呼叫樹狀檢視]。 應用程式中 CPU 使用量最高的程式代碼路徑稱為 熱點路徑。 經常性路徑火焰圖示 (
)可協助快速識別可能改善的效能問題。
在 呼叫樹狀結構 檢視中,您可以看到應用程式中 GetBlogTitleX 方法的高 CPU 使用量,佔整個應用程式 CPU 使用量的約 60%。 不過, 的 GetBlogTitleX 值為低,只有大約 .10%。 不同於 CPU 總,自身 CPU 值會排除其他函式所耗費的時間,因此我們知道要在呼叫樹中更深入地查看以找到實際瓶頸。
GetBlogTitleX 對兩個 LINQ DLL 進行外部呼叫,這些 DLL 占用了大部分的 CPU 時間,這可以從非常高的 自用 CPU 值看出。 這是 LINQ 查詢可能是要優化的區域的第一個線索。
若要取得可視化的呼叫樹狀結構和數據的不同檢視,請開啟 Flame Graph 檢視。 (或者,以滑鼠右鍵按兩下 [GetBlogTitleX],然後選擇 [火焰圖形] 中的 [檢視]。在這裡,看起來 GetBlogTitleX 方法會負責許多應用程式的CPU使用量(如黃色所示)。 對 LINQ DLL 的外部呼叫會顯示在 [GetBlogTitleX] 方塊下方,而且它們會針對 方法使用所有 CPU 時間。
收集其他數據
其他工具通常可以提供其他資訊來協助分析並隔離問題。 在此案例研究中,我們會採用下列方法:
- 首先,查看記憶體使用量。 高 CPU 使用量與高記憶體使用量之間可能存在關聯,因此查看這兩者有助於識別來源問題。
- 因為我們識別出 LINQ DLL,所以我們也會查看資料庫工具。
檢查記憶體使用量
若要查看應用程式在記憶體使用量方面的進展,我們會使用 .NET 物件配置工具收集追蹤(針對C++,您可以改用記憶體使用量工具)。 記憶體追蹤中的 呼叫樹狀結構 檢視會顯示熱門路徑,並協助我們辨識高記憶體使用量。 在這個階段,毫無意外,GetBlogTitleX 方法似乎正在產生大量的物件! 事實上,超過 900,000 個物件分配。
建立的大部分物件都是字串、物件陣列和 Int32。 我們或許可以藉由檢查原始程式碼來查看這些型別的產生方式。
檢查資料庫工具中的查詢
在 [效能分析工具] 中,我們選取 [資料庫] 工具,而不是 [CPU 使用量] (或選取兩者)。 當我們收集追蹤檔後,請在診斷頁面開啟 [查詢] 標籤。 在 [資料庫追蹤的查詢] 索引標籤中,您可以看到第一個數據列顯示最長的查詢,2446 毫秒。 記錄 欄顯示查詢讀取的記錄數目。 您可以使用這項資訊進行稍後的比較。
藉由檢查 [查詢] 數據行中 LINQ 所產生的 SELECT 語句,我們會將第一個數據列識別為與 GetBlogTitleX 方法相關聯的查詢。 若要檢視完整的查詢字串,請展開數據行寬度。 完整的查詢字串為:
SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"
請注意,應用程式在這裡擷取許多數據行值,或許比我們需要的更多。 讓我們看看原始程式碼。
優化程序代碼
是時候看看 GetBlogTitleX 原始程式碼了。 在 [資料庫] 工具中,以滑鼠右鍵按下查詢,然後選擇 移至來源檔案。 在 GetBlogTitleX的原始程式碼中,我們發現下列程式代碼會使用LINQ讀取資料庫。
foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
{
foreach (var post in blog.Posts)
{
if (post.Author == "Fred Smith")
{
Console.WriteLine($"Post: {post.Title}");
}
}
}
此程式代碼會使用 foreach 迴圈搜尋資料庫中由「Fred Smith」作為作者的任何部落格。 查看它時,您可以看到記憶體中會產生許多物件:資料庫中每個部落格的新物件陣列、每個URL的相關字串,以及文章中包含的屬性值,例如部落格標識符。
我們做一些研究,並尋找一些常見的建議,以瞭解如何優化 LINQ 查詢。 或者,我們可以節省時間,讓 科皮洛特 為我們做研究。
如果我們使用 Copilot,我們會從內容功能表選取 詢問 Copilot,然後輸入下列問題:
Can you make the LINQ query in this method faster?
提示
您可以使用像 /optimize 這樣的斜線命令,來幫助形成適合 Copilot 的良好問題。
在此範例中,Copilot 會提供下列建議的程式代碼變更,以及說明。
public void GetBlogTitleX()
{
var posts = db.Posts
.Where(post => post.Author == "Fred Smith")
.Select(post => post.Title)
.ToList();
foreach (var postTitle in posts)
{
Console.WriteLine($"Post: {postTitle}");
}
}
此程式代碼包含數項變更,可協助優化查詢:
- 已新增
Where子句,並排除其中一個foreach迴圈。 - 投影只有
Select語句中的 Title 屬性,這是我們在此範例中所需要的一切。
接下來,我們會使用分析工具重新測試。
結果
更新程式代碼之後,我們會重新執行CPU使用量工具來收集追蹤。
呼叫樹狀結構 檢視顯示,GetBlogTitleX 只執行 1754 毫秒,使用應用程式 CPU 總量的 37%,相較於之前的 59%是大幅改善。
切換至 Flame Graph 視圖,以查看另一個顯示變化的可視化圖。 在此檢視中,GetBlogTitleX 也會使用較小的CPU部分。
檢查資料庫工具追蹤中的結果,而且只會使用此查詢讀取兩筆記錄,而不是 100,000! 此外,查詢會大幅簡化,並移除了先前生成的不必要的 LEFT JOIN。
接下來,我們會在 .NET 物件配置工具中重新檢查結果,並查看 GetBlogTitleX 只負責 56,000 個物件配置,幾乎比 900,000 減少近 95 個%!
重複
可能需要多次優化,我們可以透過程式碼調整進行反覆測試,以了解哪些變更能改善效能,進而降低計算成本。
後續步驟
下列文章和部落格文章提供詳細資訊,可協助您瞭解如何有效地使用 Visual Studio 效能工具。
- 案例研究:釐清效能問題
- 案例研究:30分鐘內性能加倍
- 使用新的檢測工具 改善Visual Studio效能