最佳化程式碼並降低計算成本的初學者指南 (C#、Visual Basic、C++、F#)

減少計算時間表示降低成本,因此最佳化程式碼可以節省金錢。 在本文中,我們會展示如何使用各種分析工具,以協助您完成這項工作。

此處的意圖不是提供逐步指示,而是要向您展示如何有效地使用分析工具,以及如何解譯資料。 CPU 使用量工具可協助您擷取和視覺化應用程式中使用計算資源的位置。 呼叫樹和火焰圖等 CPU 使用量檢視提供很好的圖表視覺效果,顯示應用程式中花費時間的位置。 此外,自動深入解析可能會顯示哪些精確的最佳化可有重大影響。 其他分析工具也可協助您隔離問題。 如要比較工具,請參閱我應該選擇哪一個工具?

開始調查

  • 請採取 CPU 使用量追蹤來開始調查。 CPU 使用量工具通常有助於開始效能調查,並最佳化程式碼以降低成本。
  • 接下來,如果您想要其他深入解析來協助隔離問題或改善效能,請考慮使用其他分析工具之一收集追蹤。 例如:
    • 查看記憶體使用量。 針對 .NET,請先嘗試 .NET 物件配置工具。 針對 .NET 或 C++,您可以查看記憶體使用量工具。
    • 如果您的應用程式使用檔案 I/O,請使用檔案 I/O 工具。
    • 如果使用 ADO.NET 或 Entity Framework,您可以嘗試資料庫工具來檢查 SQL 查詢、精確的查詢時間等等。

資料收集範例

本文所示的範例螢幕擷取畫面是以針對部落格資料庫和相關聯部落格文章執行查詢的 .NET 應用程式為基礎。 您必須先檢查 CPU 使用量追蹤,以尋找最佳化程式碼和降低計算成本的機會。 在大致了解正在發生的情況後,您也會查看來自其他分析工具的追蹤,以協助隔離問題。

資料收集需要下列步驟 (此處未顯示):

  • 將您的應用程式設定為發行組建
  • 從效能分析工具中選取 CPU 使用量工具 (Alt+F2)。 (後續步驟涉及一些其他工具。)
  • 從效能分析工具中,啟動應用程式並收集追蹤。

檢查高 CPU 使用量的區域

首先使用 CPU 使用量工具收集追蹤。 當診斷資料載入時,請先檢查顯示 [熱門深入解析] 和 [最忙碌路徑] 的初始 .diagsession 報告頁面。 [最忙碌路徑] 會顯示應用程式中 CPU 使用量最高的程式碼路徑。 這些區段可能會提供一些提示,協助您快速識別可改善的效能問題。

您也可以在 [呼叫樹] 檢視中檢視最忙碌路徑。 若要開啟此檢視,請使用報告中的 [開啟詳細資料] 連結,然後選取 [呼叫樹]

在此檢視中,您會再次看到最忙碌路徑,其中顯示應用程式中 GetBlogTitleX 方法的 CPU 使用量很高,約佔應用程式 CPU 使用量的 60%。 不過,GetBlogTitleX 的 [自我 CPU] 值很低,只有大約 .10%。 不同於 [CPU 總計],[自我 CPU] 值會排除其他函式所花費的時間,因此我們知道要更深入地查看呼叫樹,以取得實際瓶頸。

CPU 使用量工具中 [呼叫樹狀結構] 檢視的螢幕擷取畫面。

GetBlogTitleX 對使用大部分 CPU 時間的兩個 LINQ DLL 進行外部呼叫,非常高的 [自我 CPU] 值證明了這一點。。 這是您可能想要尋找 LINQ 查詢作為最佳化區域的第一個線索。

CPU 使用量工具中 [呼叫樹狀結構] 檢視的螢幕擷取畫面,其中反白顯示了 Self CPU。

若要取得視覺化的呼叫樹與不同的資料檢視,請切換至 [火焰圖] 檢視 (從與 [呼叫樹] 相同的清單中選取)。 再次,看起來 GetBlogTitleX 方法佔用了應用程式的大量 CPU 使用量 (以黃色顯示)。 對 LINQ DLL 的外部呼叫會顯示在 GetBlogTitleX 方塊下方,而且其正在使用該方法的所有 CPU 時間。

CPU 使用量工具中 [Flame Graph] 檢視的螢幕擷取畫面。

收集供額資料

其他工具通常可以提供額外資訊來協助分析並隔離問題。 例如,由於已識別 LINQ DLL,我們會先嘗試資料庫工具。 您可以多次選取此工具以及 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"

請注意,您在這裡會擷取許多資料行值,或許超過您所需的。

若要查看應用程式在記憶體使用量方面發生什麼情況,請使用 .NET 物件配置工具收集追蹤 (若為 C++,請改用記憶體使用量工具)。 記憶體追蹤中的 [呼叫樹] 檢視會顯示最忙碌路徑,並協助您識別高記憶體使用量的區域。 此時不要驚訝,GetBlogTitleX 方法似乎會產生許多物件! 事實上,超過 900,000 個物件配置。

.NET 物件配置工具中 [呼叫樹狀結構] 檢視的螢幕擷取畫面。

建立的物件大部分都是字串、物件陣列和 Int32。 您可以藉由檢查原始程式碼來查看這些類型的產生方式。

最佳化程式碼

是時候看一下 GetBlogTitleX 原始程式碼了。 在 .NET 物件配置工具中,以滑鼠右鍵按一下方法,然後選擇 [移至來源檔案]。 在 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 查詢,並提出此程式碼。

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

在此程式碼中,您已進行數個變更,以協助最佳化查詢:

  • 新增 Where 子句並消除其中一個 foreach 迴圈。
  • 僅投影 Select 陳述式中的 Title 屬性,這就是本範例中您所需的一切。

接下來,使用分析工具重新測試。

檢查結果

在更新程式碼之後,請重新執行 CPU 使用量工具來收集追蹤。 [呼叫樹] 檢視會顯示 GetBlogTitleX 僅執行 1754 毫秒,使用 37% 的應用程式 CPU 總量,比起 59% 有了大幅改善。

CPU 使用量工具的 [呼叫樹狀結構] 檢視中改善的 CPU 使用量的螢幕擷取畫面。

切換至 [火焰圖] 檢視,以查看改善的另一個視覺效果。 在此檢視中,GetBlogTitleX 也使用了較小部分的 CPU。

CPU 使用量工具的 [Flame Graph] 檢視中改善的 CPU 使用量的螢幕擷取畫面。

檢查資料庫工具追蹤中的結果,使用此查詢只讀取兩筆記錄,而不是 100,000 筆! 此外,查詢會大幅簡化,並消除先前產生的不必要 LEFT JOIN。

資料庫工具中更快查詢時間的螢幕擷取畫面。

接下來,重新檢查 .NET 物件配置工具中的結果,並看到 GetBlogTitleX 只負責 56,000 個物件配置,比 900,000 個減少了近 95%!

.NET 物件配置工具中減少的記憶體配置的螢幕擷取畫面。

反覆執行

可能需要多個最佳化,而且您可以繼續逐一查看程式碼變更,以查看哪些變更可改善效能並降低計算成本。

下一步

下列部落格文章提供詳細資訊,可協助您了解如何有效地使用 Visual Studio 效能工具。