SpanOwner<T>
SpanOwner<T>
は、共有メモリ プールからバッファーを借用するスタック専用バッファー型です。 基本的に MemoryOwner<T>
の機能をミラー化していますが、型は ref struct
です。 これは、同期コード (Memory<T>
インスタンスを必要としないもの) でのみ使われる有効期間の短いバッファーや、タイトなループで実行されるコードに特に役立ちます。これは、SpanOwner<T>
値の作成にメモリ割り当てがまったく必要ないからです。
プラットフォーム API:
SpanOwner<T>
、MemoryOwner<T>
構文
MemoryOwner<T>
と同じコア機能がこの型にも適用されますが、スタック専用 struct
であることと、IMemoryOwner<T>
interface
の実装および Memory<T>
プロパティがないことを除きます。 上記の違いを除き、構文は MemoryOwner<T>
で使われるものとほぼ同じです。
たとえば、指定したサイズの一時バッファー (この値を length
と呼びます) を割り当て、それを使って何らかの作業を実行する必要があるメソッドがあるとします。 最初の非効率的なバージョンは次のようになります。
byte[] buffer = new byte[length];
// Use buffer here
これは理想的ではありません。このコードが使われるたびに新しいバッファーを割り当て、すぐにそれを破棄することになるからです (MemoryOwner<T>
のドキュメントにも記載されています)。これにより、ガベージ コレクターへの負担が大きくなります。 ArrayPool<T>
を使って上記のコードを最適化できます。
// Using directive to access the ArrayPool<T> type
using System.Buffers;
int[] buffer = ArrayPool<int>.Shared.Rent(length);
try
{
// Slice the span, as it might be larger than the requested size
Span<int> span = buffer.AsSpan(0, length);
// Use the span here
}
finally
{
ArrayPool<int>.Shared.Return(buffer);
}
上記のコードでは、配列プールからバッファーを借用していますが、より冗長でエラーが発生しやすくなっています。try/finally
ブロックで借用したバッファーを常にプールに返すように注意する必要があります。 次のように、SpanOwner<T>
型を使って書き換えることができます。
// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Buffers;
using SpanOwner<int> buffer = SpanOwner<int>.Allocate(length);
Span<int> span = buffer.Span;
// Use the span here, no slicing necessary
SpanOwner<T>
インスタンスは内部で配列を借用し、スコープ外になったときにそれをプールに戻す処理を行います。 C# コンパイラにより、using
ステートメントの展開時に自動的に追加されるため、try/finally
ブロックを使う必要はありません。 そのため、SpanOwner<T>
型は ArrayPool<T>
API の軽量なラッパーと考えることができます。よりコンパクトで使いやすくなり、有効期間の短いバッファーを適切に借用および破棄するために記述する必要のあるコードの量が減らすことができます。 SpanOwner<T>
を使うと、コードがはるかに短くなり、より簡単になることがわかります。
Note
これはスタック専用型であるため、C# 8 で導入されたダック型の IDisposable
パターンに依存します。 これは上記のサンプルに示されています。SpanOwner<T>
型は、IDisposable
インターフェイスをまったく実装しておらず、ボックス化されていないにもかかわらず、using
ブロック内で使われています。 機能はまったく同じです。バッファーがスコープ外になるとすぐに、自動的に破棄されます。 SpanOwner{T}
の API は、パフォーマンスを向上させるためにこのパターンに依存しています。つまり、SpanOwner<T>
型がスコープ内にある限り、基となるバッファーは決して破棄されないと想定し、MemoryOwner<T>
内で実行される追加のチェック (Memory<T>
または Span<T>
インスタンスを返す前に、バッファーが実際にまだ使用可能であることを確認するためのもの) を実行しません。 そのため、この型は常に using
ブロックまたは式と共に使う必要があります。 そうしないと、基となるバッファーが共有プールに返されなくなります。 技術的には、SpanOwner<T>
型で Dispose
を手動で呼び出して同じことを実現することもできます (この場合、C# 8 は必要ありません)。ただし、これはエラーが発生しやすいため、推奨されません。
例
「単体テスト」では、さらに他の例を見つけることができます。
.NET Community Toolkit