Von Bedeutung
このセクションで説明する手法では、コード内の ホット パス に適用するとパフォーマンスが向上します。 ホット パス は、通常の操作で頻繁に繰り返し実行されるコードベースのセクションです。 あまり実行されないコードにこれらの手法を適用すると、影響は最小限になります。 パフォーマンスを向上させるために変更を加える前に、ベースラインを測定することが重要です。 次に、そのベースラインを分析して、メモリのボトルネックが発生する場所を特定します。 診断とインストルメンテーションのセクションで、アプリケーションのパフォーマンスを測定するための多くのクロス プラットフォーム ツールについて学習できます。 チュートリアルのプロファイリング セッションを練習して、Visual Studio ドキュメントの メモリ使用量を測定 できます。
メモリ使用量を測定し、割り当てを減らすことができることを確認したら、このセクションの手法を使用して割り当てを減らします。 連続する変更のたびに、メモリ使用量をもう一度測定します。 各変更がアプリケーションのメモリ使用量にプラスの影響を与えるようにします。
.NET でのパフォーマンスの動作は、多くの場合、コードから割り当てを削除することです。 割り当てるすべてのメモリ ブロックは、最終的に解放される必要があります。 割り当てが少ないほど、ガベージ コレクションに費やす時間が短縮されます。 特定のコード パスからガベージ コレクションを削除することで、より予測可能な実行時間を実現できます。
割り当てを減らす一般的な方法は、重要なデータ構造を class
型から struct
型に変更することです。 この変更は、これらの型を使用するセマンティクスに影響します。 パラメーターと戻り値は、参照ではなく値で渡されるようになりました。 型が小さく、3 単語以下の場合、値をコピーするコストはごくわずかです (1 つの単語が 1 つの整数の自然なサイズであることを考慮してください)。 測定可能であり、大規模な種類の場合は実際のパフォーマンスに影響を与える可能性があります。 コピーの効果に対処するために、開発者はこれらの型を ref
渡して意図したセマンティクスを取り戻すことができます。
C# ref
機能を使用すると、全体的な使いやすさに悪影響を与えることなく、 struct
型の目的のセマンティクスを表現できます。 これらの機能強化の前には、開発者は、同じパフォーマンスへの影響を達成するために、ポインターと生メモリを用いた構造体に頼らざるを得ませんでした。 コンパイラは、新しい関連機能に対してref
を生成します。
検証可能な安全なコード は、コンパイラがバッファー オーバーランの可能性を検出するか、未割り当てまたは解放されたメモリにアクセスすることを意味します。 コンパイラは、一部のエラーを検出して防止します。
参照渡しと参照返し
C# の変数は 値を格納します。
struct
型では、値は型のインスタンスの内容です。
class
型では、値は型のインスタンスを格納するメモリ ブロックへの参照です。
ref
修飾子を追加すると、変数に値への参照が格納されます。
struct
型では、参照は値を含むストレージを指します。
class
型では、参照はメモリ ブロックへの参照を含むストレージを指します。
C# では、メソッドへのパラメーターは 値渡しされ、戻り値は 値によって返されます。 引数の 値 がメソッドに渡されます。 戻り値の引数の 値 は戻り値です。
ref
、in
、ref 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 以外のパラメーター | 現在の方法 |
ref 、 ref readonly 、 in パラメーター |
メソッドの呼び出し |
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 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
が含まれています。 そのため、スパンのインスタンスはその安全なコンテキストから離れることができません。
のref struct
は、そのの ref field
。
Memory<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
コードを必要としません。 賢明に使用すると、以前は安全でない手法を使用してのみ可能であった安全なコードからパフォーマンス特性を取得できます。
メモリ割り当ての削減に関するチュートリアルでは、この手法を自分で試すことができます。
.NET