本文內容
- 通過引用傳遞和返回
- 引用安全上下文
- 安全的上下文和 ref 結構
- 統一內存類型
- 通過參考安全提高性能
本節中介紹的技術可提高應用于代碼中的熱路徑時的性能。熱路徑是代碼庫中在正常操作中經常重復執行的部分。將這些技術應用于不經常執行的代碼將產生最小的影響。在進行任何更改以提高性能之前,測量基線至關重要。然后,分析該基線以確定內存瓶頸發生的位置。
測量內存使用情況并確定可以減少分配后,請使用本節中的技術來減少分配。每次連續更改后,再次測量內存使用情況。確保每個更改都會對應用程序中的內存使用產生積極影響。
.NET 中的性能工作通常意味著從代碼中刪除分配。您分配的每個內存塊最終都必須被釋放。較少的分配可減少垃圾回收所花費的時間。它通過從特定代碼路徑中刪除垃圾回收,允許更可預測的執行時間。
減少分配的常用策略是將關鍵數據結構從類型更改為類型。此更改會影響使用這些類型的語義。參數和返回現在按值傳遞,而不是按引用傳遞。如果類型很小,只有三個字或更少(考慮到一個字的自然大小為一個整數),則復制值的成本可以忽略不計。它是可衡量的,可以對較大的類型產生真正的性能影響。為了消除復制的影響,開發人員可以傳遞這些類型來獲取預期的語義。
使用 C# 功能,您可以表達所需的類型語義,而不會對其整體可用性產生負面影響。在實現這些增強功能之前,開發人員需要求助于具有指針和原始內存的構造,以實現相同的性能影響。編譯器為新的相關功能生成可驗證的安全代碼。可驗證的安全代碼意味著編譯器檢測到可能的緩沖區溢出或訪問未分配或釋放的內存。編譯器會檢測并防止某些錯誤。
1、通過引用傳遞和返回
C# 中的變量存儲值。在類型中,該值是該類型的實例的內容。在類型中,該值是對存儲該類型實例的內存塊的引用。添加修飾符意味著變量存儲對值的引用。在類型中,引用指向包含該值的存儲。在類型中,引用指向包含對內存塊的引用的存儲。
在 C# 中,方法的參數是按值傳遞的,返回值是按值返回的。參數的值將傳遞給該方法。返回參數的值是返回值。
或 修飾符指示參數是通過引用傳遞的。對存儲位置的引用將傳遞給該方法。添加到方法簽名意味著通過引用返回返回值。對存儲位置的引用是返回值。
您還可以使用?ref 賦值讓一個變量引用另一個變量。典型的賦值將右側的值復制到賦值左側的變量。ref 賦值將右側變量的內存位置復制到左側的變量。現在指的是原始變量:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignmentConsole.WriteLine(location); // output: 42sameLocation = 19; // assignmentConsole.WriteLine(anInteger); // output: 19
分配變量時,會更改其值。當您?ref 賦值一個變量時,您可以更改它所引用的內容。
您可以使用變量、通過引用傳遞和引用賦值直接處理值的存儲。編譯器強制執行的范圍規則可確保直接使用存儲時的安全性。
和修飾符都指示參數應通過引用傳遞,并且不能在方法中重新賦值。區別在于,該方法將參數用作變量。該方法可能會捕獲參數,也可能通過只讀引用返回參數。在這些情況下,應使用修飾符。否則,修飾符將提供更大的靈活性。您無需將修飾符添加到參數的參數中,因此您可以使用修飾符安全地更新現有 API 簽名。如果未將 or 修飾符添加到參數的參數中,編譯器將發出警告。
2、引用安全上下文
C# 包含表達式規則,以確保在表達式引用的存儲不再有效的情況下無法訪問表達式。請看以下示例:
public ref int CantEscape()
{int index = 42;return ref index; // Error: index's ref safe context is the body of CantEscape
}
編譯器報告錯誤,因為無法從方法返回對局部變量的引用。調用方無法訪問所引用的存儲。ref 安全上下文定義表達式可以安全訪問或修改的范圍。下表列出了變量類型的?ref 安全上下文。 字段不能在 a 或 non-ref 中聲明,因此這些行不在表中:
聲明 | ref 安全上下文 |
---|---|
非 ref 本地 | 聲明 local 的塊 |
non-ref 參數 | current 方法 |
ref 參數ref readonly in | 調用方法 |
out 參數 | current 方法 |
class 田 | 調用方法 |
non-ref 字段struct | current 方法 |
ref 領域ref struct | 調用方法 |
如果變量的?ref 安全上下文是調用方法,則可以返回該變量。如果其?ref 安全上下文是當前方法或塊,則不允許返回。以下代碼片段顯示了兩個示例。可以從調用方法的作用域訪問成員字段,因此類或結構字段的?ref 安全上下文是調用方法。帶有 或修飾符的參數的?ref 安全上下文是整個方法。兩者都可以從成員方法返回:
private int anIndex;public ref int RetrieveIndexRef()
{return ref anIndex;
}public ref int RefMin(ref int left, ref int right)
{if (left < right)return ref left;elsereturn ref right;
}
編譯器確保引用無法轉義其引用安全上下文。您可以安全地使用參數、 和局部變量,因為編譯器會檢測您是否意外編寫了代碼,在表達式存儲無效時可以訪問表達式。
3、安全的上下文和 ref 結構
ref struct
類型需要更多的規則來確保它們可以安全使用。類型可以包含字段。這需要引入一個安全的環境。對于大多數類型,安全上下文是調用方法。換言之,始終可以從方法返回不是 ref struct
ref
ref struct
的值。
非正式地,a?的安全上下文是可以訪問其所有字段的范圍。換句話說,它是其所有字段的?ref 安全上下文的交集。以下方法返回 a to a member 字段,因此其安全上下文是該方法:ref struct
ref
ref
ReadOnlySpan<char>
private string longMessage = "This is a long message";public ReadOnlySpan<char> Safe()
{var span = longMessage.AsSpan();return span;
}
相反,以下代碼會發出錯誤,因為 的成員引用了堆棧分配的整數數組。它無法轉義方法:ref field
Span<int>
public Span<int> M()
{int length = 3;Span<int> numbers = stackalloc int[length];for (var i = 0; i < length; i++){numbers[i] = i;}return numbers; // Error! numbers can't escape this method.
}
4、統一內存類型
System.Span<T> 和?System.Memory<T>?的引入為使用內存提供了統一的模型。System.ReadOnlySpan<T> 和?System.ReadOnlyMemory<T>?提供用于訪問內存的只讀版本。它們都提供了對存儲類似元素數組的內存塊的抽象。區別在于 和 是類型,而 和 是類型。跨度包含一個 .因此,跨度的實例不能離開其安全上下文。a?的安全上下文是其?的 ref 安全上下文。實施并刪除此限制。您可以使用這些類型直接訪問內存緩沖區。Span<T>
ReadOnlySpan<T>
ref struct
Memory<T>
ReadOnlyMemory<T>
struct
ref field
ref struct
ref field
Memory<T>
ReadOnlyMemory<T>
5、通過參考安全提高性能
使用這些功能提高性能涉及以下任務:
- 避免分配:將類型從 a 更改為 時,會更改其存儲方式。局部變量存儲在堆棧上。分配容器對象時,成員以內聯方式存儲。此更改意味著分配更少,從而減少了垃圾回收器所做的工作。它還可能會降低內存壓力,從而降低垃圾回收器的運行頻率。
- 保留引用語義:將類型從 a 更改為 a 會更改將變量傳遞給方法的語義。修改其參數狀態的代碼需要修改。現在參數是 ,該方法正在修改原始對象的副本。您可以通過將該參數作為參數傳遞來還原原始語義。更改后,該方法將再次修改原始內容。
- 避免復制數據:復制較大的類型可能會影響某些代碼路徑的性能。還可以添加修飾符,以便通過引用而不是按值將較大的數據結構傳遞給方法。
- 限制修改:當通過引用傳遞類型時,被調用的方法可以修改結構的狀態。您可以將修飾符替換為 or 修飾符,以指示無法修改參數。當方法捕獲參數或通過只讀引用返回參數時,首選。還可以創建類型或具有成員的類型,以便更好地控制可以修改的成員。
- 直接操作內存:當將數據結構視為包含一系列元素的內存塊時,某些算法最有效。和類型提供對內存塊的安全訪問。
這些技術都不需要代碼。如果使用得當,您可以從安全代碼中獲得性能特征,而以前只能使用不安全技術才能實現。