次の方法で共有


新しい C# 機能を使用してメモリ割り当てを削減する

Von Bedeutung

このセクションで説明する手法では、コード内の ホット パス に適用するとパフォーマンスが向上します。 ホット パス は、通常の操作で頻繁に繰り返し実行されるコードベースのセクションです。 あまり実行されないコードにこれらの手法を適用すると、影響は最小限になります。 パフォーマンスを向上させるために変更を加える前に、ベースラインを測定することが重要です。 次に、そのベースラインを分析して、メモリのボトルネックが発生する場所を特定します。 診断とインストルメンテーションのセクションで、アプリケーションのパフォーマンスを測定するための多くのクロス プラットフォーム ツールについて学習できます。 チュートリアルのプロファイリング セッションを練習して、Visual Studio ドキュメントの メモリ使用量を測定 できます。

メモリ使用量を測定し、割り当てを減らすことができることを確認したら、このセクションの手法を使用して割り当てを減らします。 連続する変更のたびに、メモリ使用量をもう一度測定します。 各変更がアプリケーションのメモリ使用量にプラスの影響を与えるようにします。

.NET でのパフォーマンスの動作は、多くの場合、コードから割り当てを削除することです。 割り当てるすべてのメモリ ブロックは、最終的に解放される必要があります。 割り当てが少ないほど、ガベージ コレクションに費やす時間が短縮されます。 特定のコード パスからガベージ コレクションを削除することで、より予測可能な実行時間を実現できます。

割り当てを減らす一般的な方法は、重要なデータ構造を class 型から struct 型に変更することです。 この変更は、これらの型を使用するセマンティクスに影響します。 パラメーターと戻り値は、参照ではなく値で渡されるようになりました。 型が小さく、3 単語以下の場合、値をコピーするコストはごくわずかです (1 つの単語が 1 つの整数の自然なサイズであることを考慮してください)。 測定可能であり、大規模な種類の場合は実際のパフォーマンスに影響を与える可能性があります。 コピーの効果に対処するために、開発者はこれらの型を ref 渡して意図したセマンティクスを取り戻すことができます。

C# ref 機能を使用すると、全体的な使いやすさに悪影響を与えることなく、 struct 型の目的のセマンティクスを表現できます。 これらの機能強化の前には、開発者は、同じパフォーマンスへの影響を達成するために、ポインターと生メモリを用いた構造体に頼らざるを得ませんでした。 コンパイラは、新しい関連機能に対してrefを生成します。 検証可能な安全なコード は、コンパイラがバッファー オーバーランの可能性を検出するか、未割り当てまたは解放されたメモリにアクセスすることを意味します。 コンパイラは、一部のエラーを検出して防止します。

参照渡しと参照返し

C# の変数は を格納します。 struct型では、値は型のインスタンスの内容です。 class型では、値は型のインスタンスを格納するメモリ ブロックへの参照です。 ref修飾子を追加すると、変数に値への参照が格納されます。 struct型では、参照は値を含むストレージを指します。 class型では、参照はメモリ ブロックへの参照を含むストレージを指します。

C# では、メソッドへのパラメーターは 値渡しされ、戻り値は 値によって返されます。 引数の がメソッドに渡されます。 戻り値の引数の は戻り値です。

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 readonly修飾子とin修飾子は、引数を参照渡しで渡す必要があり、メソッドで再割り当てできないことを示します。 違いは、 ref readonly メソッドがパラメーターを変数として使用することを示す点です。 このメソッドは、パラメーターをキャプチャするか、読み取りonly 参照によってパラメーターを返す場合があります。 このような場合は、 ref readonly 修飾子を使用する必要があります。 それ以外の場合は、 in 修飾子の柔軟性が向上します。 in修飾子を使用して既存の API シグネチャを安全に更新できるように、in パラメーターの引数にin修飾子を追加する必要はありません。 ref パラメーターの引数にinまたはref readonly修飾子を追加しない場合、コンパイラは警告を発行します。

Ref セーフ コンテキスト

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 safe コンテキストは、ref式が安全にアクセスまたは変更できるスコープを定義します。 次の表に、変数型の ref セーフ コンテキストを 示します。 ref フィールドは class または ref 以外の structで宣言できないため、これらの行はテーブル内にありません。

申告 リファレンス安全コンテキスト
ref 以外のローカル変数 local が宣言されているブロック
ref 以外のパラメーター 現在の方法
refref readonlyin パラメーター メソッドの呼び出し
out パラメーター 現在の方法
class フィールド メソッドの呼び出し
ref 以外の struct フィールド 現在の方法
ref フィールド ref struct メソッドの呼び出し

変数は、refが呼び出し元のメソッドである場合に返すことができます。 ref セーフ コンテキストが現在のメソッドまたはブロックの場合、ref戻りは許可されません。 次のスニペットは、2 つの例を示しています。 メンバー フィールドは、メソッドを呼び出すスコープからアクセスできるため、クラスまたは構造体フィールドの 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 readonlyまたはin修飾子がパラメーターに適用されている場合、そのパラメーターはref readonlyではなくrefで返すことができます。

コンパイラは、参照が 参照セーフ コンテキストをエスケープできないようにします。 refパラメーター、ref return、およびrefローカル変数を安全に使用できます。これは、ストレージが有効でないときに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 fieldSpan<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 が含まれています。 そのため、スパンのインスタンスはその安全なコンテキストから離れることができません。 ref structは、そのref fieldMemory<T>ReadOnlyMemory<T>の実装により、この制限は削除されます。 これらの型を使用して、メモリ バッファーに直接アクセスします。

ref の安全性を活かしてパフォーマンスを向上させる

これらの機能を使用してパフォーマンスを向上させるには、次のタスクが必要です。

  • 割り当てを回避する: 型を class から structに変更すると、その格納方法が変更されます。 ローカル変数はスタックに格納されます。 メンバーは、コンテナー オブジェクトが割り当てられるとインラインで格納されます。 この変更は、割り当てが少なくなり、ガベージ コレクターの作業が減少することを意味します。 また、メモリの負荷が減り、ガベージ コレクターの実行頻度が低くなる可能性もあります。
  • 参照セマンティクスを保持する: 型を class から struct に変更すると、変数をメソッドに渡すセマンティクスが変更されます。 パラメーターの状態を変更したコードには、変更が必要です。 パラメーターが structになったので、メソッドは元のオブジェクトのコピーを変更しています。 元のセマンティクスを復元する場合は、そのパラメーターを ref パラメーターとして渡します。 その変更後、メソッドは元の struct をもう一度変更します。
  • データのコピーを避ける: より大きな struct 型をコピーすると、一部のコード パスのパフォーマンスに影響する可能性があります。 ref修飾子を追加して、より大きなデータ構造を値ではなく参照によってメソッドに渡すこともできます。
  • 変更を制限する: struct 型が参照によって渡されると、呼び出されたメソッドによって構造体の状態が変更される可能性があります。 ref修飾子をref readonlyまたはin修飾子に置き換えて、引数を変更できないことを示すことができます。 メソッドがパラメーターをキャプチャする場合、または読み取りonly 参照によってパラメーターを返す場合は、 ref readonly を優先します。 readonly struct メンバーを使用してstruct型またはreadonly型を作成して、変更できるstructのメンバーをより詳細に制御することもできます。
  • メモリを直接操作する: 一部のアルゴリズムは、一連の要素を含むメモリのブロックとしてデータ構造を扱うときに最も効率的です。 Span型とMemory型は、メモリ ブロックへの安全なアクセスを提供します。

これらの手法のいずれも、 unsafe コードを必要としません。 賢明に使用すると、以前は安全でない手法を使用してのみ可能であった安全なコードからパフォーマンス特性を取得できます。 メモリ割り当ての削減に関するチュートリアルでは、この手法を自分で試すことができます。