共用方式為


使用新的 C# 功能減少記憶體配置

這很重要

本節所述的技術可改善在程式碼中套用至 經常性路徑 時的效能。 熱路徑 是指那些在一般操作中頻繁執行的程式碼部分。 將這些技術套用至不常執行的程式代碼將影響最小。 在進行任何變更以改善效能之前,請務必測量基準。 然後,分析該基準,以判斷記憶體瓶頸發生的位置。 您可以在 診斷和檢測一節中瞭解許多跨平臺工具,以測量應用程式的效能。 您可以在教學課程中練習分析會話,以在 Visual Studio 文件中 測量記憶體使用量

測量記憶體使用量並判斷您可以減少配置之後,請使用本節中的技術來減少配置。 每次連續變更之後,再次測量記憶體使用量。 請確定每個變更都會對應用程式中的記憶體使用量產生正面影響。

.NET 中的效能工作通常表示從程式碼中移除配置。 您配置的每個記憶體區塊最終都必須釋放。 較少的配置可減少垃圾收集所花費的時間。 它允許從特定程式代碼路徑移除垃圾收集,以取得更可預測的運行時間。

減少配置的共同策略是將關鍵數據結構從 class 類型變更為 struct 類型。 這項變更會影響使用這些類型的語意。 參數和回傳值現在是以值方式傳遞,而不是以引用方式傳遞。 如果型別很小、三個字或更少,複製值的成本是微不足道的(考慮到一個單字是一個整數的自然大小)。 這種情況是可測量的,並且可能對較大的資料類型有真正的效能影響。 為了對抗複製的效果,開發人員可以藉由 ref 傳遞這些類型來取回預期的語意。

C# ref 特性讓您能夠表達所需的類型 struct 語義,而不會負面影響其整體可用性。 在這些增強功能之前,開發人員需要使用unsafe構造、指標和原始記憶體,才能達到相同的效能表現。 編譯程式會為新的相關功能產生ref可驗證的安全程式代碼 表示編譯程式會偵測可能的緩衝區滿溢或存取未配置或釋放的記憶體。 編譯程式會偵測並防止某些錯誤。

以參考傳遞和傳回

C# 中的變數會儲存 。 在 struct 類型中,值是型別實例的內容。 在 class 類別中,值是指向存儲類別實例的內存區塊的參考。 ref加入修飾詞表示變數會儲存值的參考。 在 struct 類型中,參考會指向包含值的記憶體。 在 class 類型中,指標會指向包含記憶體區塊引用的儲存空間。

在 C# 中,方法的參數會 以傳值方式傳遞,而回傳值也會 以傳值方式回傳。 自變數 的值 會傳遞至 方法。 return 自變數 的值 是傳回值。

refinref readonly、 或 out 修飾詞表示自變數是以傳址方式傳遞。 儲存位置的 參考 會傳遞至 方法。 新增 ref 至方法簽名表示傳回值會 以參考方式傳回。 儲存位置的 參考 是傳回值。

您也可以使用 ref 指派 讓變數參考另一個變數。 一般指派會將右側 的值 複製到指派左側的變數。 ref 指派會將右側變數的記憶體位置複製到左側的變數。 現在 ref 會參考原始變數:

int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment

Console.WriteLine(location); // output: 42

sameLocation = 19; // assignment

Console.WriteLine(anInteger); // output: 19

當您 指派 變數時,會變更其 。 當您對變數進行引用指派時,您會更改其指向的內容。

您可以直接透過 ref 變數與值的儲存空間進行操作,並使用傳址方式和 ref 指派。 編譯程式強制執行的範圍規則可確保直接使用記憶體時的安全性。

ref readonlyin 修飾詞都表示自變數應該以傳址方式傳遞,而且無法在方法中重新指派。 差異在於 ref readonly ,表示方法使用 參數做為變數。 方法可能會擷取參數,或藉由只讀參考傳回參數。 在這些情況下,您應該使用 ref readonly 修飾詞。 否則, in 修飾詞可提供更大的彈性。 您不需要為 in 參數增加 in 修飾詞,因此您可以安全地使用 in 修飾詞更新現有的 API 簽章。 如果您未將 refin 修飾詞新增至 ref readonly 參數的引數,編譯器會發出警告。

參考安全環境

C# 包含 ref 表達式的規則,以確保不能存取 ref 表達式所參考的無效記憶體。 請考慮下列範例:

public ref int CantEscape()
{
    int index = 42;
    return ref index; // Error: index's ref safe context is the body of CantEscape
}

編譯程式會報告錯誤,因為您無法從 方法傳回局部變數的參考。 呼叫端無法存取所參考的記憶體。 ref 安全內容會定義表達式安全存取或修改的範圍ref。 下表列出變數類型的 ref 安全內容ref 欄位無法在class或非 ref 的struct中宣告,因此這些數據列不在數據表中:

聲明 ref 安全環境
非參照本機 區塊,其中宣告local
非引用參數 目前的方法
refref readonlyin 參數 呼叫方法
out 參數 目前的方法
class 欄位 呼叫方法
非引用 struct 欄位 目前的方法
refref struct 呼叫方法

如果變數的 ref是呼叫方法,則可以傳回變數。 如果 ref 安全內容 是目前的方法或區塊, ref 則不允許傳回 。 下列代碼段顯示兩個範例。 您可以從呼叫方法的範圍存取成員欄位,因此類別或結構欄位的 ref 安全內容 是呼叫方法。 具有 ref 修飾詞之參數的 in是整個方法。 這兩者都可以 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;
    else
        return ref right;
}

備註

ref readonlyin修飾詞套用至參數時,參數可以由ref readonly傳回,而不是ref

編譯器可確保參照無法逸出其 ref 安全內容。 您可以安全地使用 ref 參數、ref returnref 局部變數,因為編譯器會偵測您是否不小心撰寫了當記憶體無效時仍可能存取的ref 表達式。

安全內容和 ref 結構

ref struct 類型需要更多規則,以確保可以安全地使用它們。 ref struct類型可以包含ref欄位。 這需要引入 安全內容。 對大部分類型而言, 安全內容 是呼叫方法。 換句話說,不是 ref struct 的值一律可以從 方法傳回。

非正式地說,安全上下文指的是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.
}

整合記憶體類型

引進 System.Span<T>System.Memory<T> 提供了一個統一的模型來處理記憶體。 System.ReadOnlySpan<T>System.ReadOnlyMemory<T> 提供唯讀版本來存取記憶體。 它們全都會針對儲存類似元素陣列的記憶體區塊提供抽象概念。 差別在於 Span<T>ReadOnlySpan<T>ref struct 型別,而 Memory<T>ReadOnlyMemory<T>struct 型別。 ref field 出現在 spans 中。 因此,範圍實例無法離開其安全內容的安全內容ref struct是 其ref fieldMemory<T>ReadOnlyMemory<T> 的實作移除此限制。 您可以使用這些類型直接存取記憶體緩衝區。

使用 ref 安全性改善效能

使用這些功能來改善效能牽涉到下列工作:

  • 避免配置:當您將型別從 class 變更為 struct時,您會變更其保存方式。 局部變數會儲存在堆疊上。 元素會在配置容器物件時以內聯方式儲存。 這項變更表示記憶體分配較少,且會減少垃圾收集器所需的工作量。 它也可能會降低記憶體壓力,因此垃圾收集器的執行次數減少。
  • 保留參考語意:將型別從 class 變更為 struct 將變數傳遞至方法的語意。 修改其參數狀態的程式代碼需要修改。 現在參數是 struct,方法正在修改原始對象的複本。 您可以將該參數傳遞為 ref 參數,以還原原始語意。 在該變更之後,方法會再次修改原始的 struct
  • 避免複製數據:複製較大的 struct 類型可能會影響某些程式代碼路徑中的效能。 您也可以新增 ref 修飾詞,以傳參考方式而非傳值方式將較大資料結構傳遞至方法。
  • 限制修改:以傳址方式傳遞類型時 struct ,呼叫的方法可以修改結構的狀態。 您可以將 ref 修飾詞替換為 ref readonlyin 修飾詞,以指示參數無法被修改。 當方法擷取參數或透過唯讀參考傳回時,偏好使用 ref readonly。 您也可以建立readonly struct型或具有struct成員的readonly型別,以更好地控制struct可以被修改的成員。
  • 直接操作記憶體:當將資料結構視為包含元素序列的記憶體區塊時,某些演算法最有效率的時候。 SpanMemory 型別提供記憶體區塊的安全存取。

這些技術都不需要 unsafe 程序代碼。 明智地使用時,您可以從先前只能使用不安全技術的安全程序代碼取得效能特性。 您可以在減少 記憶體配置教學課程中自行嘗試技術。