共用方式為


Performance Tips and Tricks in .NET Applications (.NET 應用程式中的效能祕訣和訣竅)

 

Emmanuel Schanzer
Microsoft Corporation

2001 年 8 月

總結: 本文適用于想要在受管理世界中調整其應用程式以獲得最佳效能的開發人員。 範例程式碼、說明和設計指導方針適用于資料庫、Windows Forms和 ASP 應用程式,以及 Microsoft Visual Basic 和 Managed C++ 的語言特定秘訣。 (25 個列印頁面)

目錄

概觀
所有應用程式的效能秘訣
資料庫存取的秘訣
ASP.NET 應用程式的效能秘訣
在 Visual Basic 中移植和開發的秘訣
在 Managed C++ 中移植和開發的秘訣
其他資源
附錄:虛擬呼叫和配置的成本

概觀

本白皮書設計為撰寫適用于 .NET 之應用程式的開發人員參考,並尋找各種改善效能的方式。 如果您是 .NET 新手的開發人員,您應該熟悉平臺和您選擇的語言。 本白皮書嚴格以該知識為基礎,並假設程式設計人員已經知道足以讓程式執行。 如果您要將現有的應用程式移植到 .NET,在開始埠之前,值得閱讀這份檔。 這裡的一些秘訣在設計階段很有説明,並提供在開始埠之前應該注意的資訊。

這份檔分成區段,並以專案和開發人員類型組織提示。 第一組秘訣是一組必須讀取才能以任何語言撰寫,並包含可協助您在 Common Language Runtime (CLR) 上使用任何目的語言的建議。 相關章節會遵循 ASP 特定的秘訣。 第二組秘訣會依語言組織,處理使用 Managed C++ 和 Microsoft® Visual Basic® 的特定秘訣。

由於排程限制,第 1 版 (v1) 執行時間必須先以最廣泛的功能為目標,之後再處理特殊案例優化。 這會導致一些效能變成問題的漏洞案例。 因此,本檔涵蓋數個設計來避免這種情況的秘訣。 這些秘訣不會與下一個版本 (vNext) 相關,因為這些案例有系統地識別並優化。 我會在進行時加以指出,而您決定是否值得投入心力。

所有應用程式的效能秘訣

使用任何語言處理 CLR 時,有一些要記住的秘訣。 這些與每個人有關,而且應該是處理效能問題時的第一道防線。

擲回較少的例外狀況

擲回例外狀況可能非常昂貴,因此請確定您不會擲回許多例外狀況。 使用 Perfmon 查看應用程式擲回的例外狀況數目。 您可能會意外發現應用程式的某些區域擲回的例外狀況會比您預期更多的例外狀況。 若要取得更好的細微性,您也可以使用效能計數器,以程式設計方式檢查例外狀況號碼。

尋找和設計繁重例外狀況的程式碼可能會導致良好的效能勝出。 請記住,這與 try/catch 區塊無關: 只有在擲回實際例外狀況時才會產生成本。 您可以視需要使用多個 try/catch 區塊。 使用例外狀況不雅之處是您失去效能的地方。 例如,您應該遠離使用控制流程例外狀況之類的專案。

以下是成本高昂例外狀況的簡單範例:我們只會透過 For 迴圈執行、產生數千個或例外狀況,然後終止。 請嘗試將 throw 語句標記為批註,以查看速度的差異:這些例外狀況會產生極大的額外負荷。

public static void Main(string[] args){
  int j = 0;
  for(int i = 0; i < 10000; i++){
    try{   
      j = i;
      throw new System.Exception();
    } catch {}
  }
  System.Console.Write(j);
  return;   
}
  • 小心! 執行時間可以自行擲回例外狀況! 例如, Response.Redirect () 會擲回 ThreadAbort 例外狀況。 即使您未明確擲回例外狀況,您也可以使用確實執行的函式。 請務必檢查 Perfmon 以取得真實故事,以及檢查來源的偵錯工具。
  • 對 Visual Basic 開發人員:Visual Basic 預設會開啟 int 檢查,以確保溢位和零除例外狀況等專案。 您可能想要關閉此功能以提升效能。
  • 如果您使用 COM,請記住,HRESULTS 可以傳回為例外狀況。 請確定您仔細追蹤這些。

進行區塊式呼叫

區塊式呼叫是執行數項工作的函式呼叫,例如初始化物件數個欄位的方法。 這是針對聊天呼叫進行檢視,該呼叫會執行非常簡單的工作,而且需要多次呼叫才能完成 (,例如設定具有不同呼叫) 之物件的每個欄位。 請務必進行區塊化,而不是在超出簡單 AppDomain 方法呼叫的額外負荷高於的方法之間聊天呼叫。 P/Invoke、Interop 和遠端呼叫都會產生額外負荷,而您想要謹慎使用它們。 在這些情況下,您應該嘗試設計應用程式,使其不依賴具有大量額外負荷的小型頻繁呼叫。

每當從 Unmanaged 程式碼呼叫 Managed 程式碼時,就會發生轉換,反之亦然。 執行時間讓程式設計人員執行 Interop 變得非常容易,但這是效能價格。 發生轉換時,必須採取下列步驟:

  • 執行資料封送處理
  • 修正呼叫慣例
  • 保護被呼叫端儲存的暫存器
  • 切換執行緒模式,讓 GC 不會封鎖非受控執行緒
  • 對 Managed 程式碼呼叫產生例外狀況處理框架
  • 控制執行緒 (選擇性)

若要加速轉換時間,請嘗試在您可以時使用 P/Invoke。 如果需要資料封送處理,則額外負荷幾乎只有 31 個指示加上封送處理的成本,否則只有 8 個。 COM Interop 的成本較高,超過 65 個指示。

資料封送處理不一定昂貴。 基本類型幾乎不需要封送處理,而且具有明確配置的類別也很便宜。 資料轉譯期間會實際變慢,例如從 ASCI 到 Unicode 的文字轉換。 請確定在 Managed 界限之間傳遞的資料只有在需要時才會轉換:可能只是在程式中同意特定資料類型或格式,您就可以減少許多封送處理額外負荷。

下列類型稱為 blittable,這表示它們可以直接複製到 Managed/Unmanaged 界限,而不需要封送處理:sbyte、byte、short、ushort、int、uint、long、ulong、float 和 double。 您可以免費傳遞這些專案,以及包含 Blittable 類型的 ValueTypes 和單一維度陣列。 您可以在 MSDN Library 進一步探索 封送處理的詳細 資料。 如果您花很多時間封送處理,建議您仔細閱讀。

使用 ValueTypes 設計

當您可以,以及當您不執行許多 Boxing 和 unboxing 時,請使用簡單的結構。 以下是示範速度差異的簡單範例:

using System;

namespace ConsoleApplication{

  public struct foo{
    public foo(double arg){ this.y = arg; }
    public double y;
  }
  public class bar{
    public bar(double arg){ this.y = arg; }
    public double y;
  }
  class Class1{
    static void Main(string[] args){
      System.Console.WriteLine("starting struct loop...");
      for(int i = 0; i < 50000000; i++)
      {foo test = new foo(3.14);}
      System.Console.WriteLine("struct loop complete. 
                                starting object loop...");
      for(int i = 0; i < 50000000; i++)
      {bar test2 = new bar(3.14); }
      System.Console.WriteLine("All done");
    }
  }
}

當您執行此範例時,您會看到結構迴圈的速度較快。 不過,當您將 ValueType 視為物件時,請務必注意使用 ValueTypes。 這會為您的程式增加額外的 Boxing 和 Unboxing 額外負荷,而且如果遇到物件停滯,最終會花費更多成本! 若要查看此動作,請修改上述程式碼以使用 foos 和橫條陣列。 您會發現效能較不相等。

權衡 ValueTypes 的彈性遠低於 Objects,而且如果不正確使用,則最終會降低效能。 您必須仔細瞭解使用時機和方式。

請嘗試修改上述範例,並將 foos 和橫條儲存在陣列或雜湊表內。 您會看到速度消失,只是使用 一個 Boxing 和 Unboxing 作業。

您可以查看 GC 配置和集合,以追蹤 Box 和 unbox 的頻率。 這可以在程式碼中使用 Perfmon 外部或效能計數器來完成。

請參閱.NET Framework 中Run-Time技術效能考慮中的 ValueTypes 深入討論。

使用 AddRange 新增群組

使用 AddRange 來新增整個集合,而不是反復新增集合中的每個專案。 幾乎所有的視窗控制項和集合都有 AddAddRange 方法,而且每個方法都已針對不同的用途進行優化。 Add 適用于新增單一專案,而 AddRange 有一些額外的額外負荷,但在新增多個專案時會勝出。 以下是幾個支援 AddAddRange的類別:

  • StringCollection、TraceCollection 等。
  • HttpWebRequest
  • UserControl
  • ColumnHeader

修剪工作集

將您用來讓工作集保持較小的元件數目降到最低。 如果您只載入整個元件以使用一個方法,您需支付非常少的權益費用。 查看您是否可以使用您已載入的程式碼複製該方法的功能。

追蹤您的工作集很困難,而且可能是整份檔的主旨。 以下是一些協助您解決的秘訣:

  • 使用vadump.exe來追蹤您的工作集。 這在另一份白皮書中討論,涵蓋受管理環境的各種工具。
  • 查看 Perfmon 或效能計數器。 他們可以提供您載入類別數目的詳細意見反應,或取得 JITed 的方法數目。 您可以取得您在載入器中花費多少時間,或執行時間百分比花費分頁的讀取。

針對字串反復專案使用 For 迴圈— 第 1 版

在 C# 中, foreach 關鍵字可讓您跨清單、字串等專案進行逐步解說。並在每個專案上執行作業。 這是非常強大的工具,因為它可作為許多類型的一般用途列舉值。 此一般化取捨是速度,而且如果您依賴字串反復專案,您應該改用 For 迴圈。 由於字串是簡單的字元陣列,因此可以使用比其他結構較少的額外負荷來逐步解說。 在許多) 情況下,JIT 的智慧度足以 (,以優化 For 迴圈內的界限檢查和其他專案,但禁止在 foreach 逐步解說上執行此動作。 最終結果是在第 1 版中,字串上的 For 迴圈最多比使用 foreach快五倍。 這會在未來的版本中變更,但對於第 1 版,這是提高效能的明確方式。

以下是用來示範速度差異的簡單測試方法。 請嘗試執行它,然後移除 For 迴圈並取消批註 foreach 語句。 在我的電腦上, For 迴圈大約需要一秒,而 foreach 語句大約需要 3 秒。

public static void Main(string[] args) {
  string s = "monkeys!";
  int dummy = 0;

  System.Text.StringBuilder sb = new System.Text.StringBuilder(s);
  for(int i = 0; i < 1000000; i++)
    sb.Append(s);
  s = sb.ToString();
  //foreach (char c in s) dummy++;
  for (int i = 0; i < 1000000; i++)
    dummy++;
  return;   
  }
}

Foreach 取捨遠更容易閱讀,未來會像For迴圈一樣快速地讀取字串等特殊案例。 除非字串操作是您真正的效能,否則稍微雜亂的程式碼可能不值得。

使用 StringBuilder 進行複雜的字串操作

修改字串時,執行時間會建立新的字串並傳回它,讓原始字串保持垃圾收集。 在大部分情況下,這是快速且簡單的做法,但是當重複修改字串時,它就會開始負擔效能:所有這些配置最終都會變得昂貴。 以下是附加至字串 50,000 次的程式簡單範例,後面接著一個使用 StringBuilder 物件來就地修改字串的程式。 StringBuilder程式碼會更快,而且如果您執行這些程式碼會立即明顯。

namespace ConsoleApplication1.Feedback{
  using System;
  
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      String str = test.text;
      for(int i=0;i<50000;i++){
        str = str + "blue_toothbrush";
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}
namespace ConsoleApplication1.Feedback{
  using System;
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      System.Text.StringBuilder SB = 
        new System.Text.StringBuilder(test.text);
      for(int i=0;i<50000;i++){
        SB.Append("blue_toothbrush");
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}

請嘗試查看 Perfmon,以查看儲存的時間量,而不需要配置數千個字串。 查看 .NET CLR 記憶體清單底下的 「% time in GC」 計數器。 您也可以追蹤您儲存的配置數目,以及收集統計資料。

取捨= 在時間和記憶體中建立 StringBuilder 物件時,會有一些相關聯的額外負荷。 在具有快速記憶體的電腦上,如果您執行大約五項作業, StringBuilder 就會變得值得。 根據經驗法則,我假設 10 或多個字串作業是任何電腦上額外負荷的理由,即使是較慢的字串作業也一樣。

先行編譯Windows Forms應用程式

第一次使用方法時會是 JITed,這表示如果您的應用程式在啟動期間執行許多方法呼叫,您就會支付較大的啟動懲罰。 Windows Forms在 OS 中使用許多共用程式庫,啟動它們的額外負荷可能會高於其他類型的應用程式。 雖然並非一定情況,但先行編譯Windows Forms應用程式通常會導致效能勝出。 在其他案例中,最好讓 JIT 負責處理它,但如果您是Windows Forms開發人員,您可能會想要查看。

Microsoft 可讓您呼叫 ngen.exe 來預先編譯應用程式。 您可以選擇在安裝期間或發佈應用程式之前執行ngen.exe。 請務必在安裝期間執行ngen.exe,因為您可以確定應用程式已針對安裝所在的機器進行優化。 如果您在寄送程式之前執行ngen.exe,請將優化限制為 電腦上 可用的優化。 為了讓您瞭解預先編譯可以協助多少,我已在我的電腦上執行非正式測試。 以下是 ShowFormComplex 的冷啟動時間,這是一個具有大約一百個控制項的 winforms 應用程式。

程式碼狀態 時間
Framework JITed

ShowFormComplex JITed

3.4 秒
架構先行編譯,ShowFormComplex JITed 2.5 秒
架構先行編譯,ShowFormComplex 先行編譯 2.1sec

每個測試都是在重新開機後執行。 如您所見,Windows Forms應用程式會預先使用許多方法,使其成為預先編譯的效能勝出。

使用不規則陣列— 第 1 版

v1 JIT 會將不區分的陣列優化 (只比矩形陣列更有效率的'arrays-of-arrays') ,而差異相當明顯。 以下表格示範使用不規則陣列取代 C# 和 Visual Basic 中矩形陣列所產生的效能提升, (較高的數位較好) :

  C# Visual Basic 7
工作分派 (不完全)

指派 (矩形)

14.16

8.37

12.24

8.62

類神經網路 (不完全)

類神經網路 (矩形)

4.48

3.00

4.58

3.13

數值排序 (不規則)

數值排序 (矩形)

4.88

2.05

5.07

2.06

指派基準測試是簡單的指派演算法,從商務用 量化決策 (Gordon、Pressman 和 Cohn 中找到的逐步指南進行調整;Prentice-Hall;列印) 。 類神經網路測試會透過小型神經網路執行一系列模式,而數值排序是自我說明的。 結合在一起,這些基準測試代表真實世界效能的良好指示。

如您所見,使用不規則陣列可能會導致大幅提升效能。 對不規則陣列所做的優化將會新增至未來的 JIT 版本,但針對 v1,您可以使用不規則陣列來節省大量時間。

保留介於 4KB 和 8KB 之間的 IO 緩衝區大小

對於幾乎每個應用程式,介於 4KB 到 8KB 之間的緩衝區將可提供最大效能。 對於非常特定的實例,您可以從較大的緩衝區取得改善, (載入可預測大小的大型影像,例如) ,但在 99.99% 的情況下,它只會浪費記憶體。 所有衍生自 BufferedStream 的緩衝區都可讓您將大小設定為您想要的任何專案,但在大多數情況下,4 和 8 會提供最佳效能。

在 Lookout 上尋找非同步 IO 商機

在罕見的情況下,您可能能夠受益于非同步 IO。 其中一個範例可能是下載並解壓縮一系列檔案:您可以從一個資料流程讀取中的位、在 CPU 上解碼,並將其寫到另一個資料流程。 有效使用非同步 IO 需要很多心力,如果效能不正確,可能會導致效能 遺失 。 優點是正確套用時,非同步 IO 可提供您效能的十倍。

MSDN Library 上提供 使用非同步 IO 之程式的絕佳範例

  • 請注意,非同步呼叫有一點安全性額外負荷:叫用非同步呼叫時,會擷取呼叫端堆疊的安全性狀態,並傳輸至實際執行要求的執行緒。 如果回呼執行大量程式碼,或非同步呼叫未過度使用,這可能不是問題

資料庫存取的秘訣

調整資料庫存取權的原理只是使用您所需的功能,以及設計「中斷連線」方法:依序進行數個連線,而不是長時間開啟單一連線。 您應該將此變更納入考慮,並加以設計。

Microsoft 建議 多層式策略,以達到最大效能,而不是直接用戶端對資料庫連線。 請將這視為設計原理的一部分,因為許多技術都已優化,以利用多頭性案例。

使用最佳受控提供者

選擇正確的 Managed 提供者,而不是依賴泛型存取子。 有專為許多不同的資料庫撰寫的受控提供者,例如 SQL (System.Data.SqlClient) 。 如果您在使用特製化元件時使用更通用的介面,例如 System.Data.Odbc,您將失去處理新增間接層級的效能。 使用最佳提供者也可以讓您說出不同的語言:Managed SQL 用戶端會將 TDS 讀出 SQL 資料庫,進而大幅改善一般 OleDbprotocol。

當您可以時,挑選資料集上的資料讀取器

每當您不需要保留資料時,請使用資料讀取器。 這可快速讀取資料,如果使用者需要,可以快取這些資料。 讀取器只是一種無狀態資料流程,可讓您在資料送達時讀取資料,然後卸載資料,而不需要將其儲存至資料集以取得更多流覽。 串流方法較快,而且負擔較少,因為您可以立即開始使用資料。 您應該評估需要相同資料的頻率,以決定流覽的快取是否適合您。 以下是一個小型資料表,示範從伺服器提取資料時,ODBC 和 SQL 提供者上的 DataReader 和 DataSet 之間的差異, (較高的數位較好) :

  ADO SQL
DataSet 801 2507
DataReader 1083 4585

如您所見,使用最佳受控提供者與資料讀取器時,會達到最高的效能。 當您不需要快取資料時,使用資料讀取器可提供大幅的效能提升。

針對 MP 電腦使用Mscorsvr.dll

對於獨立仲介層和伺服器應用程式,請確定 mscorsvr 用於多處理器電腦。 Mscorwks 未針對調整或輸送量進行優化,而伺服器版本有數個優化,可在有多個處理器可用時妥善調整。

盡可能使用預存程式

預存程式是高度優化的工具,可在有效使用時產生絕佳的效能。 設定預存程式,以使用資料配接器處理插入、更新和刪除。 預存程式不需要解譯、編譯或甚至從用戶端傳輸,並同時減少網路流量和伺服器額外負荷。 請務必使用 CommandType.StoredProcedure,而不是 CommandType.Text

請小心處理動態連接字串

連線共用是重複使用多個要求連線的實用方式,而不是為每個要求支付開啟和關閉連線的額外負荷。 它會隱含完成,但您會 為每個唯一的連接字串取得一個集區。 如果您要動態產生連接字串,請確定每次發生共用時字串都相同。 另請注意,如果發生委派,您就會為每個使用者取得一個集區。 您可以為連線集區設定許多選項,而且您可以使用 Perfmon 來追蹤回應時間、交易/秒等專案,以追蹤集區的效能。

關閉您不使用的功能

如果不需要,請關閉自動交易登記。 針對 SQL 受控提供者,它會透過連接字串來完成:

SqlConnection conn = new SqlConnection(
"Server=mysrv01;
Integrated Security=true;
Enlist=false");

使用資料配接器填入資料集時,如果您不需要 (,請勿取得主鍵資訊,例如,請勿設定 MissingSchemaAction.Add with key) :

public DataSet SelectSqlSrvRows(DataSet dataset,string connection,string query){
    SqlConnection conn = new SqlConnection(connection);
    SqlDataAdapter adapter = new SqlDataAdapter();
    adapter.SelectCommand = new SqlCommand(query, conn);
    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
    adapter.Fill(dataset);
    return dataset;
}

避免自動產生的命令

使用資料配接器時,請避免自動產生的命令。 這些需要額外的伺服器車程來擷取中繼資料,並提供較低層級的互動控制。 雖然使用自動產生的命令很方便,但值得在效能關鍵性應用程式中自行執行。

注意 ADO 舊版設計

請注意,當您在配接器上執行命令或呼叫 fill 時,會傳回查詢所指定的每一筆記錄。

如果絕對需要伺服器資料指標,則可以透過 t-sql 中的預存程式來實作它們。 請盡可能避免,因為伺服器資料指標型實作無法妥善調整。

如有需要,請以無狀態和無連線方式實作分頁。 您可以透過下列方式,將其他記錄新增至資料集:

  • 確定 PK 資訊存在
  • 視需要變更資料配接器的 select 命令,以及
  • 呼叫填滿

讓資料集保持精簡

只將您需要的記錄放入資料集中。 請記住,資料集會將所有資料儲存在記憶體中,而且您要求的資料越多,透過網路傳輸所需的時間就越長。

盡可能使用循序存取

使用資料讀取器時,請使用 CommandBehavior.SequentialAccess。 這是處理 Blob 資料類型的必要條件,因為它允許以小型區塊從網路讀取資料。 雖然您一次只能使用一個資料片段,但載入大型資料類型的延遲會消失。 如果您不需要一次處理整個物件,使用循序存取可為您提供更佳的效能。

ASP.NET 應用程式的效能秘訣

積極快取

使用 ASP.NET 設計應用程式時,請確定您設計時會留意快取。 在作業系統的伺服器版本上,您有許多選項可用來調整伺服器和用戶端上的快取使用方式。 ASP 中有數個功能與工具可供您用來提升效能。

輸出快取— 儲存 ASP 要求的靜態結果。 使用 指示詞指定 <@% OutputCache %>

  • 持續時間 - 快取中存在時間專案
  • VaryByParam—依 Get/Post 參數而有所不同快取專案
  • VaryByHeader— 依 Http 標頭而有所不同的快取專案
  • VaryByCustom —依瀏覽器而有所不同的快取專案
  • 覆寫以因您想要的任何專案而有所不同:
    • 片段快取— 當無法儲存整個頁面 (隱私權、個人化、動態內容) 時,您可以使用片段快取來儲存其部分,以便稍後更快速地擷取。

      ) VaryByControl— 會依控制項的值來變更快取的專案

    • 快取 API— 藉由將快取物件的雜湊表保留在記憶體中, (System.web.UI.cacheing) ,提供非常精細的快取細微性。 它也:

      ) 包含相依性 (金鑰、檔案、時間)

      b) 自動過期未使用的專案

      c) 支援回呼

以智慧方式快取可提供絕佳的效能,而且請務必考慮您需要的快取類型。 假設有數個靜態頁面可供登入的複雜電子商務網站,然後產生包含影像和文字的動態產生頁面。 您可能想要針對這些登入頁面使用輸出快取,然後針對動態頁面使用片段快取。 例如,工具列可以快取為片段。 為了提升效能,您可以使用快取 API 快取經常出現在網站上的常用影像和重複使用文字。 如需使用範例程式碼) 快取 (的詳細資訊,請參閱 ASP.NET 網站。

只有在您需要時才使用會話狀態

ASP.NET 功能非常強大,就是能夠為使用者儲存會話狀態,例如電子商務網站或瀏覽器歷程記錄上的購物車。 由於預設為開啟狀態,因此即使您未使用它,仍需支付記憶體中的成本。 如果您未使用會話狀態,請將@% EnabledSessionState = false % > 新增 <至 asp,以關閉它並節省額外負荷。 這會隨附數個其他選項,這些選項會在 ASP.NET 網站中說明。

對於只有讀取會話狀態的頁面,您可以選擇 EnabledSessionState=readonly。 這負擔比完整讀取/寫入會話狀態少,而且當您只需要部分功能,而且不想支付寫入功能的費用時,這非常有用。

只有在您需要時才使用檢視狀態

檢視狀態的範例可能是使用者必須填寫的長表單:如果他們按一下瀏覽器中的 [ 上一頁 ],然後返回,表單會維持填滿狀態。 未使用此功能時,此狀態會耗用記憶體和效能。 或許此處最大的效能清空是每次載入頁面以更新並驗證快取時,都必須透過網路傳送來回訊號。 由於預設為開啟狀態,因此您必須指定不想使用檢視狀態與 < @% EnabledViewState = false % > 。 您應該深入瞭解 ASP.NET 網站上的檢視狀態,以瞭解您可以存取的其他一些選項和設定。

避免 STA COM

Apartment COM 的設計目的是要處理非受控環境中的執行緒。 Apartment COM 有兩種:單一執行緒和多執行緒。 MTA COM 的設計目的是要處理多執行緒,而 STA COM 則依賴傳訊系統來序列化執行緒要求。 受控世界是自由執行緒,而使用單一線程 Apartment COM 需要所有 Unmanaged 執行緒基本上都會共用單一線程以進行 Interop。 這會導致 大幅 達到效能,並應盡可能避免。 如果您無法將 Apartment COM 物件移植到受管理世界,請使用< @%AspCompat = 「true」 % >代表使用這些物件的頁面。 如需 STA COM 的更詳細說明,請參閱 MSDN 程式庫。

批次編譯

在將大型頁面部署至 Web 之前,一律先批次處理編譯。 這可以藉由對每個目錄的頁面執行一個要求,並等候 CPU 再次閒置來起始。 這可防止網頁伺服器因為編譯而停滯,同時嘗試提供頁面。

移除不必要的 Http 模組

根據所使用的功能,從管線移除未使用或不必要的 HTTP 模組。 回收新增的記憶體和浪費的迴圈,可提供小速度提升。

避免 Autoeventwireup 功能

不要依賴 autoeventwireup,而是覆寫來自 Page 的事件。 例如,嘗試多載 public void OnLoad () 方法,而不是撰寫Page_Load (# B3 方法。 這可讓執行時間不必針對每個頁面執行 CreateDelegate ()

當您不需要 UTF 時,使用 ASCII 進行編碼

根據預設,ASP.NET 會設定為將要求和回應編碼為 UTF-8。 如果 ASCII 是您的所有應用程式需求,則消除 UTF 額外負荷可能會讓您傳回幾個週期。 請注意,這只能以個別應用程式為基礎來完成。

使用最佳驗證程式

有幾種不同的方式可以驗證使用者,有些比其他 (成本更高的方法:無、Windows、Forms、Passport) 。 請確定您使用最符合您需求的最便宜專案。

在 Visual Basic 中移植和開發的秘訣

從 Microsoft Visual Basic® 6 到 Microsoft®® Visual Basic® 7 的幕後,許多變更了效能對應。 由於 CLR 的新增功能和安全性限制,某些函式只會像在 Visual Basic 6 中一樣快速執行。 事實上,Visual Basic 7 有數個區域會由其前置任務進行擷取。 幸運的是,有兩個好消息:

  • 大部分最差的速度會在一次性函式期間發生,例如第一次載入控制項。 成本存在,但您只需支付一次。
  • Visual Basic 7 有許多區域較快,而且這些區域通常位於執行時間重複的函式中。 這表示權益會隨著時間成長,而且在數種情況下,將會超過一次性成本。

大部分的效能問題都來自執行時間不支援 Visual Basic 6 功能的區域,而且必須新增以在 Visual Basic 7 中保留功能。 在執行時間以外工作較慢,讓某些功能更耗費使用成本。 亮面是您可以避免這些問題,並花費一些心力。 有兩個主要區域需要工作才能優化效能,而您可以在這裡和該處進行幾個簡單的調整。 結合在一起,這些可協助您逐步執行效能清空,並利用 Visual Basic 7 中更快速的函式。

錯誤處理

第一個考慮是錯誤處理。 這在 Visual Basic 7 中已變更很多,而且有與變更相關的效能問題。 基本上,實作 OnErrorGotoResume 所需的邏輯相當昂貴。 建議您快速查看您的程式碼,並醒目提示您使用 Err 物件的所有區域,或任何錯誤處理機制。 現在查看每個實例,並查看您是否可以重寫它們以使用 try/catch。 許多開發人員都發現,他們可以針對這些案例輕鬆地轉換來 試用/攔截 ,而且他們應該會在程式中看到良好的效能改進。 經驗法則為「如果您可以輕鬆地看到翻譯,請執行此動作」。

以下是相較于try/catch版本,使用On Error Goto的簡單 Visual Basic 程式範例。

Sub SubWithError()
On Error Goto SWETrap
  Dim x As Integer
  Dim y As Integer
  x = x / y
SWETrap:  Exit Sub
  End Sub
 
Sub SubWithErrorResumeLabel()
  On Error Goto SWERLTrap
  Dim x As Integer
  Dim y As Integer
  x = x / y 
SWERLTrap:
  Resume SWERLExit
  End Sub
SWERLExit:
  Exit Sub
Sub SubWithError()
  Dim x As Integer
  Dim y As Integer
  Try    x = x / y  Catch    Return  End Try
  End Sub
 
Sub SubWithErrorResumeLabel()
  Dim x As Integer
  Dim y As Integer
  Try
    x = x / y
  Catch
  Goto SWERLExit
  End Try
 
SWERLExit:
  Return
  End Sub

速度增加很明顯。 SubWithError () 使用 OnErrorGoto需要 244 毫秒,而且只有 169 毫秒使用 try/catch。 相較于優化版本的 164 毫秒,第二個函式需要 179 毫秒。

使用早期系結

第二個考慮會處理物件和類型傳送。 Visual Basic 6 在幕後執行許多工作來支持對象轉型,而許多程式設計人員甚至不知道它。 在 Visual Basic 7 中,這是一個您可以從中排除許多效能的區域。 當您編譯時,請使用 早期系結。 這可告知編譯器只有在明確提及時才會插入 類型強制 型轉。 這有兩個主要影響:

  • 奇怪的錯誤更容易追蹤。
  • 消除不必要的強制型轉,因而大幅改善效能。

當您使用物件時,就像是不同類型的物件一樣,如果您未指定,Visual Basic 會為您強制轉換物件。 這很方便,因為程式設計人員必須擔心較少的程式碼。 缺點是這些強制型轉可以執行非預期的情況,而且程式設計人員無法控制它們。

在某些情況下,您必須使用晚期繫結,但如果您不確定的話,大部分時候都可以使用早期系結。 對於 Visual Basic 6 程式設計人員而言,這一開始可能有點麻煩,因為您必須擔心類型超過過去。 這應該很容易供新程式設計人員使用,且熟悉 Visual Basic 6 的人員不會及時加以挑選。

開啟 [嚴格] 和 [明確] 選項

在 [選項嚴格] 開啟時,您可以保護自己免于不小心晚期繫結,並強制執行較高層級的程式碼撰寫專業領域。 如需具有 Option Strict 的限制清單,請參閱 MSDN Library。 請注意,必須明確指定所有縮小類型強制型別。 不過,這本身可能會發現程式碼的其他區段執行的工作比您先前所想的還要多,而且可能有助於您在程式中產生一些 Bug。

Option Explicit 比 Option Strict 更不嚴格,但仍會強制程式設計人員在其程式碼中提供更多資訊。 具體而言,您必須先宣告變數,才能使用它。 這會將類型推斷從執行時間移至編譯時間。 這消除檢查會為您轉譯為新增的效能。

建議您從 [選項明確] 開始,然後開啟 Option Strict。 這可保護您的編譯器錯誤,並可讓您逐漸開始在更嚴格的環境中工作。 使用這兩個選項時,您可確保應用程式的最大效能。

使用文字的二進位比較

比較文字時,請使用二進位比較,而不是文字比較。 在執行時間,二進位的額外負荷會比較輕。

最小化使用 Format ()

當您可以使用 toString () 而不是 format () 。 在大部分情況下,其會提供您所需的功能,而額外負荷較少。

使用 Charw

使用 charw 而非 char。 CLR 會在內部使用 Unicode,而且如果使用,則必須在執行時間轉譯 char 。 這可能會導致大幅的效能遺失,並指定您的字元是完整字長 (使用 charw ) 可消除此轉換。

優化指派

使用 exp += val ,而不是 exp = exp + val。 由於exp 可能任意複雜,因此這可能會導致許多不必要的工作。 這會強制 JIT 評估 exp的複本,而不需要多次此複本。 第一個語句的優化遠優於第二個語句,因為 JIT 可以避免評估 exp 兩次。

避免不必要的間接性

當您使用 byRef時,會傳遞指標,而不是實際物件。 這在許多情況下 (副作用函式,例如) ,但您不一定需要它。 傳遞指標會產生比存取堆疊上的值更慢的間接性。 當您不需要流覽堆積時,最好避免它。

將串連放在一個運算式中

如果您在多行上有多個串連,請嘗試將所有串連放在一個運算式上。 編譯器可以藉由就地修改字串來優化,以提供速度和記憶體提升。 如果語句分割成多行,Visual Basic 編譯器將不會產生 Microsoft 中繼語言 (MSIL) ,以允許就地串連。 請參閱稍早討論的 StringBuilder 範例。

包含 Return 語句

Visual Basic 允許函式傳回值,而不使用 return 語句。 雖然 Visual Basic 7 支援此功能,但明確使用 return 可讓 JIT 執行稍微更多的優化。 如果沒有 return 語句,每個函式都會在堆疊上提供數個區域變數,以透明方式支援傳回不含 關鍵字的值。 讓 JIT 更難進行優化,而且可能會影響程式碼的效能。 查看函式並視需要插入 傳回 。 它完全不會變更程式碼的語意,而且可協助您從應用程式取得更多速度。

在 Managed C++ 中移植和開發的秘訣

Microsoft 的目標是受控 C++ (MC++) 特定開發人員集。 MC++ 不是每個作業的最佳工具。 閱讀本檔之後,您可能會決定 C++ 不是最佳工具,而且取捨成本不值得優點。 如果您不確定 MC++,有許多良好的 資源 可協助您做出決策。本節的目標是已經決定他們想要以某種方式使用 MC++ 的開發人員,並想要瞭解其效能層面。

對於 C++ 開發人員,工作受控 C++ 需要做出數個決策。 您要移植一些舊的程式碼嗎? 如果是,您要將整個專案移至受控空間,還是要改為規劃實作包裝函式? 我將會專注于「埠一切」選項,或基於此討論的目的,處理從頭撰寫 MC++,因為這些是程式設計人員會注意到效能差異的案例。

受管理世界的優點

Managed C++ 最強大的功能是能夠在 運算式層級混合和比對 Managed 和 Unmanaged 程式碼。 沒有其他語言可讓您這樣做,而且有一些強大的優點可供正確使用。 我稍後將逐步解說一些範例。

受控世界也提供您龐大的設計勝出,在中,有許多常見問題會為您處理。 您可以視需要將記憶體管理、執行緒排程和類型強制型別保留給執行時間,讓您將焦點放在需要程式的元件上。 使用 MC++,您可以選擇想要保留多少控制項。

MC++ 程式設計人員在編譯為 IL 時,能夠使用 Microsoft Visual C® 7 (VC7) 後端,然後在其中使用 JIT。 用來與 Microsoft C++ 編譯器搭配運作的程式設計人員,是用來讓專案快速運作。 JIT 是使用不同的目標所設計,而且有一組不同的優缺點。 VC7 編譯器不受 JIT 的時間限制所系結,可以執行 JIT 無法進行的特定優化,例如整個程式分析、更積極內嵌和註冊。 另外還有一些優化只能在類型afe 環境中執行,讓速度比 C++ 允許的空間還多。

由於 JIT 中的優先順序不同,有些作業的速度會比之前快,而其他作業則較慢。 有一些您為安全性和語言彈性所做的取捨,其中有些並不便宜。 幸運的是,程式設計人員可以執行的動作可將成本降到最低。

移植:所有 C++ 程式碼都可以編譯為 MSIL

在進一步進行之前,請務必注意,您可以將 任何 C++ 程式碼編譯成 MSIL。 所有專案都會運作,但不保證型別安全,而且如果您執行許多 Interop,您就會支付封送處理懲罰。 如果您未獲得任何優點,為什麼編譯為 MSIL 會很有説明? 在移植大型程式碼基底的情況下,這可讓您逐步移植程式碼。 如果您使用的是 MC++,則可以花時間移植更多程式碼,而不是撰寫特殊包裝函式,以將移植且尚未移植的程式碼黏附在一起,這可能會導致大勝。 它讓移植應用程式成為非常簡潔的程式。 若要深入瞭解如何將 C++ 編譯為 MSIL,請參閱 /clr 編譯器選項

不過,只要將您的 C++ 程式碼編譯為 MSIL,就不會提供受控世界的安全性或彈性。 您必須以 MC++ 和 v1 撰寫,這表示放棄一些功能。 下列清單在目前版本的 CLR 中不受支援,但未來可能支援。 Microsoft 先選擇支援最常見的功能,必須剪下一些其他功能才能寄送。 沒有防止稍後新增它們,但同時,您必須執行下列動作:

  • 多重繼承
  • 範本
  • 確定性最終化

如果您需要這些功能,您一律可以與不安全的程式碼互通,但您將會針對封送處理資料來回支付效能負面影響。 請記住,這些功能只能在 Unmanaged 程式碼內使用。 受控空間不知道其存在。 如果您決定移植程式碼,請考慮您依賴設計中的這些功能。 在少數情況下,重新設計的成本太高,而您想要繼續使用 Unmanaged 程式碼。 這是開始駭客攻擊之前,您應該進行的第一個決策。

MC++ 優於 C# 或 Visual Basic 的優點

來自非受控背景,MC++ 會保留許多處理不安全程式碼的能力。 MC++能夠順暢地混合 Managed 和 Unmanaged 程式碼,讓開發人員擁有大量的能力,而且您可以在撰寫程式碼時選擇您想要放置的漸層位置。 在一個極端上,您可以直接、未經修改的 C++ 撰寫所有專案,並只使用 /clr進行編譯。 另一方面,您可以將所有專案寫入為 Managed 物件,並處理上述語言限制和效能問題。

但是,當您在兩者之間選擇某個位置時,MC++ 的實際功能就來了。 MC++ 可讓您藉由精確控制何時使用不安全的功能,來調整 Managed 程式碼固有的一些效能點擊。 C# 在 unsafe 關鍵字中有一些這項功能,但它不是語言不可或缺的一部分,而且它比 MC++ 還少。 讓我們逐步解說一些範例,其中顯示 MC++ 中可用的更細微度,我們將討論其實用情況。

一般化的 「byref」 指標

在 C# 中,您只能藉由將它傳遞至 ref 參數,來取得類別某些成員的位址。 在 MC++ 中,byref 指標是一種第一級建構。 您可以在陣列中間取得專案的位址,並從函式傳回該位址:

Byte* AddrInArray( Byte b[] ) {
   return &b[5];
}

我們利用這項功能透過協助程式常式傳回 System.String 中 「characters」 的指標,甚至可以使用這些指標迴圈陣列:

System::Char* PtrToStringChars(System::String*);   
for( Char*pC = PtrToStringChars(S"boo");
  pC != NULL;
  pC++ )
{
      ... *pC ...
}

您也可以透過在 MC++ 中使用插入來執行連結清單周遊,方法是擷取您無法在 C#) 中執行的 「next」 欄位位址 (:

Node **w = &Head;
while(true) {
  if( *w == 0 || val < (*w)->val ) {
    Node *t = new Node(val,*w);
    *w = t;
    break;
  }
  w = &(*w)->next;
}

在 C# 中,您無法指向 「Head」,或擷取 「next」 欄位的位址,因此您必須在第一個位置插入的特殊案例,或如果 「Head」 為 Null。 此外,您必須在程式碼中隨時查看一個節點。 將這與良好的 C# 會產生什麼比較:

if( Head==null || val < Head.val ) {
  Node t = new Node(val,Head);
  Head = t;
}else{
  // we know at least one node exists,
  // so we can look 1 node ahead
  Node w=Head;
while(true) {
  if( w.next == null || val < w.next.val ){
    Node t = new Node(val,w.next.next);
    w.next = t;
    break;
  }
  w = w.next;
  }
}         

使用者存取 Boxed 類型

OO 語言常見的效能問題是 Boxing 和 Unboxing 值所花費的時間。 MC++ 可讓您更充分地控制此行為,因此您不需要動態 (或靜態) unbox 存取值。 這是另一個效能增強功能。 只要將 __box 關鍵字放在 任何類型之前,即可代表其 Boxed 表單:

__value struct V {
  int i;
};
int main() {
  V v = {10};
  __box V *pbV = __box(v);
  pbV->i += 10;           // update without casting
}

在 C# 中,您必須將資料夾取消收件匣至 「v」,然後更新值,然後重新方塊回到物件:

struct B { public int i; }
static void Main() {
  B b = new B();
  b.i = 5;
  object o = b;         // implicit box
  B b2 = (B)o;            // explicit unbox
  b2.i++;               // update
  o = b2;               // implicit re-box
}

STL 集合與受控集合 - v1

不良消息:在 C++ 中,使用 STL 集合通常就像手動撰寫該功能一樣快速。 CLR 架構非常快速,但遇到 Boxing 和 unboxing 問題:所有專案都是物件,而且沒有範本或泛型支援,所有動作都必須在執行時間檢查。

好消息:在長期中,您可以確定此問題會在將泛型新增至執行時間時消失。 您目前部署的程式碼將會體驗速度提升,而不會有任何變更。 在短期內,您可以使用靜態轉換來防止檢查,但這已不再安全。 建議您在效能絕對重要的緊密程式碼中使用這個方法,並識別出兩個或三個作用點。

使用 Stack Managed 物件

在 C++ 中,您會指定物件應該由堆疊或堆積管理。 您仍然可以在 MC++ 中執行這項操作,但您應該注意的限制。 CLR 會針對所有堆疊管理的物件使用 ValueTypes,而且 ValueTypes 可以執行的作業有一項限制,例如) (。 如需詳細資訊 ,請參閱 MSDN 程式庫。

邊角案例:注意 Managed 程式碼內的間接呼叫 — v1

在 v1 執行時間中,所有間接函式呼叫都會以原生方式進行,因此需要轉換至 Unmanaged 空間。 任何間接函式呼叫只能從原生模式進行,這表示來自 Managed 程式碼的所有間接呼叫都需要 Managed 到 Unmanaged 轉換。 當資料表傳回 Managed 函式時,這是嚴重的問題,因為必須進行 第二 次轉換才能執行函式。 相較于執行單一 通話 指令的成本,成本會比 C++ 慢 5 到 100 倍!

幸運的是,當您呼叫位於垃圾收集類別內的方法時,優化會移除此問題。 不過,在已使用 /clr編譯的一般 C++ 檔案的特定案例中,方法傳回會被視為 Managed。 由於優化無法移除此功能,因此您會遇到完整的雙轉換成本。 以下是這類案例的範例。

//////////////////////// a.h:    //////////////////////////
class X {
public:
   void mf1();
   void mf2();
};

typedef void (X::*pMFunc_t)();


////////////// a.cpp: compiled with /clr  /////////////////
#include "a.h"

int main(){
   pMFunc_t pmf1 = &X::mf1;
   pMFunc_t pmf2 = &X::mf2;

   X *pX = new X();
   (pX->*pmf1)();
   (pX->*pmf2)();

   return 0;
}


////////////// b.cpp: compiled without /clr /////////////////
#include "a.h"

void X::mf1(){}


////////////// c.cpp: compiled with /clr ////////////////////
#include "a.h"
void X::mf2(){}

有數種方式可以避免此問題:

  • 將 類別設為 Managed 類別 (「__gc」)
  • 如果可能的話,請移除間接呼叫
  • 讓類別保持編譯為 Unmanaged 程式碼 (例如請勿使用 /clr)

將效能點擊降至最低— 第 1 版

在 1 版 JIT 下,MC++ 中只有數個作業或功能成本較高。 我會列出它們,並提供一些說明,然後我們將討論您可以做什麼。

  • 抽象概念- 這是一個區域,其中 C++ 後端編譯器在 JIT 上非常繁重。 如果您將 int 包裝在類別內以進行抽象概念,而且您嚴格以 int 的形式存取它,C++ 編譯器可以降低包裝函式的額外負荷,實際上不會有任何額外負荷。 您可以將許多抽象層級新增至包裝函式,而不需要增加成本。 JIT 無法花一些時間來消除此成本,使得 MC++ 中的深度抽象概念更昂貴。
  • 浮點數- v1 JIT 目前不會執行 VC++ 後端執行的所有 FP 特定優化,讓浮點作業目前成本更高。
  • 多維度陣列— JIT 更適合處理不規則陣列,而不是多維度陣列,因此請改用不規則陣列。
  • 64 位算術- 在未來的版本中,64 位優化將會新增至 JIT。

您可以執行的動作

在開發的每個階段,您都有數件事可以執行。 使用 MC++ 時,設計階段可能是最重要的區域,因為它會決定您最終執行的工作量,以及您傳回的效能。 當您下頭撰寫或移植應用程式時,您應該考慮下列事項:

  • 識別您使用多個繼承、範本或決定性最終處理的區域。 您必須移除這些內容,否則請將該程式碼的一部分保留在 Unmanaged 空間中。 思考重新設計的成本,並找出可移植的區域。
  • 找出效能作用點,例如跨受控空間的深度抽象概念或虛擬函式呼叫。 這些也需要設計決策。
  • 尋找已指定為堆疊管理的物件。 請確定它們可以轉換成 ValueTypes。 將其他物件標示為要轉換成堆積管理的物件。

在撰寫程式碼階段,您應該注意成本較高的作業,以及處理這些作業的選項。 MC++ 的其中一個最好事項是,您必須先掌握所有效能問題,再開始撰寫程式碼:這有助於稍後剖析工作。 不過,當您撰寫程式碼和偵錯時,仍可以執行一些調整。

判斷哪些區域會大量使用浮點算術、多維度陣列或程式庫函式。 這些區域有哪些是效能關鍵? 流量分析工具來挑選負擔最多成本的片段,並挑選最適合的選項:

  • 將整個片段保留在 Unmanaged 空間中。
  • 在程式庫存取上使用靜態轉換。
  • 請嘗試調整 boxing/unboxing 行為, (稍後) 說明。
  • 撰寫您自己的結構。

最後,將您所做的轉換數目降到最低。 如果您有一些非受控程式碼或位於迴圈中的 Interop 呼叫,請將整個迴圈設為 Unmanaged。 如此一來,您只會支付轉換成本兩次,而不是針對迴圈的每個反復專案付費。

其他資源

.NET Framework效能的相關主題包括:

觀看目前正在開發的未來文章,包括設計概觀、架構和程式碼撰寫原理、受控世界中效能分析工具的逐步解說,以及目前可用的其他企業應用程式的效能比較。

附錄:虛擬通話和配置的成本

通話類型 # Calls/sec
ValueType 非虛擬呼叫 809971805.600
類別非虛擬呼叫 268478412.546
類別虛擬呼叫 109117738.369
ValueType Virtual (Obj 方法) 呼叫 3004286.205
ValueType Virtual (覆寫 Obj 方法) 呼叫 2917140.844
新增 (非靜態) 載入類型 1434.720
新增虛擬方法 (載入類型) 1369.863

注意 測試機器是 PWINDOWS 733Mhz,執行 Windows 2000 Professional 與 Service Pack 2。

此圖表會比較與不同類型的方法呼叫相關聯的成本,以及具現化包含虛擬方法的類型成本。 數位愈高,每秒可以執行呼叫/具現化次數越多。 雖然這些號碼在不同的機器和設定上確實會有所不同,但對另一個呼叫執行一個呼叫的相對成本仍然相當重要。

  • ValueType 非虛擬呼叫:此測試會呼叫 ValueType 中包含的空白非虛擬方法。
  • 類別非虛擬呼叫:此測試會呼叫類別中包含的空白非虛擬方法。
  • 類別虛擬呼叫:此測試會呼叫類別中包含的空白虛擬方法。
  • ValueType Virtual (Obj 方法) 呼叫:此測試會呼叫 ToString () (ValueType 上的虛擬方法) ,其會採用預設物件方法。
  • ValueType Virtual (覆寫 Obj 方法) 呼叫:此測試會呼叫 ToString () (已覆寫預設值的 ValueType 上的虛擬方法) 。
  • 新增 (靜態) 載入類型:此測試只會為只有靜態方法的類別配置空間。
  • 新增 (虛擬方法) 載入類型:此測試會為具有虛擬方法的類別配置空間。

您可以繪製的一個結論是 ,當您 在類別中呼叫方法時,虛擬函式呼叫的成本大約是一般呼叫的兩倍。 請記住,通話的開頭很便宜,因此我不會移除所有虛擬通話。 當您這麼做時,應該一律使用虛擬方法。

  • JIT 無法內嵌虛擬方法,因此如果您移除非虛擬方法,就會失去潛在的優化。
  • 為具有虛擬方法的物件配置空間比沒有虛擬方法的物件配置稍微慢一點,因為必須執行額外的工作才能尋找虛擬資料表的空間。

請注意,在 ValueType 中呼叫非虛擬方法的速度比類別快三倍以上,但一 旦您將它 視為類別,就會失去嚴重性。 這是 ValueTypes 的特性:將它們視為結構,而且其光源快速。 將它們視為類別,而且速度很慢。 ToString () 是虛擬方法,因此在呼叫之前,結構必須轉換成堆積上的 物件。 在 ValueType 上呼叫虛擬方法不是兩倍的速度,而是十八倍的速度變慢! 故事的道德嗎? 請勿將 ValueTypes 視為類別。

如果您有關于本文的問題或意見,請連絡Claudio Caldato,程式經理以取得.NET Framework效能問題。