スレッドセーフな非ブロッキングバッファーマネージャーの提案
-
05-07-2019 - |
質問
非同期ソケットで使用する単純なバッファマネージャクラスを作成しました。これにより、メモリの断片化から保護され、パフォーマンスが向上します。さらなる改善や他のアプローチのための提案はありますか?
public class BufferManager
{
private int[] free;
private byte[] buffer;
private readonly int blocksize;
public BufferManager(int count, int blocksize)
{
buffer = new byte[count * blocksize];
free = new int[count];
this.blocksize = blocksize;
for (int i = 0; i < count; i++)
free[i] = 1;
}
public void SetBuffer(SocketAsyncEventArgs args)
{
for (int i = 0; i < free.Length; i++)
{
if (1 == Interlocked.CompareExchange(ref free[i], 0, 1))
{
args.SetBuffer(buffer, i * blocksize, blocksize);
return;
}
}
args.SetBuffer(new byte[blocksize], 0, blocksize);
}
public void FreeBuffer(SocketAsyncEventArgs args)
{
int offset = args.Offset;
byte[] buff = args.Buffer;
args.SetBuffer(null, 0, 0);
if (buffer == buff)
free[offset / blocksize] = 1;
}
}
解決
編集:
以下の元の回答は、過度に密結合のコード構築の問題に対処しています。ただし、ソリューション全体を考慮すると、1つの大きなバッファーを使用して、この方法でスライスを渡すことは避けます。コードをバッファーオーバーランにさらします(これをバッファーの「アンダーラン」問題と呼びます)。代わりに、それぞれが個別のバッファであるバイト配列の配列を管理します。引き渡されるオフセットは常に0で、サイズは常にバッファーの長さです。境界を越えてパーツを読み書きしようとする不正なコードはすべてキャッチされます。
元の回答
クラスをSocketAsyncEventArgsに結合しました。実際に必要なのは、バッファーを割り当てる関数で、SetBufferを次のように変更します。-
public void SetBuffer(Action<byte[], int, int> fnSet)
{
for (int i = 0; i < free.Length; i++)
{
if (1 == Interlocked.CompareExchange(ref free[i], 0, 1))
{
fnSet(buffer, i * blocksize, blocksize);
return;
}
}
fnSet(new byte[blocksize], 0, blocksize);
}
これで、次のようなコードを消費して呼び出すことができます:-
myMgr.SetBuffer((buf, offset, size) => myArgs.SetBuffer(buf, offset, size));
この場合、型の推論が buf、offset、size
の型を解決するのに十分かどうかわかりません。そうでない場合は、引数リストに型を配置する必要があります:-
myMgr.SetBuffer((byte[] buf, int offset, int size) => myArgs.SetBuffer(buf, offset, size));
ただし、クラスを使用して、非常に一般的なbyte []、int、intパターンも使用するあらゆる種類の要件にバッファーを割り当てることができます。
もちろん、無料操作を切り離す必要がありますが、それは次のとおりです:-
public void FreeBuffer(byte[] buff, int offset)
{
if (buffer == buff)
free[offset / blocksize] = 1;
}
これには、 SocketAsyncEventArgs
の場合、コードを消費してEventArgsでSetBufferを呼び出す必要があります。このアプローチがバッファの解放とソケット使用からのバッファの削除の原子性を低下させることに懸念がある場合は、この調整済みバッファマネージャをサブクラス化し、サブクラスに SocketAsyncEventArgs
特定のコードを含めます。
他のヒント
まったく異なるアプローチで新しいクラスを作成しました。
バイト配列を受け取るサーバークラスがあります。次に、他のクラスが処理できるように、バッファオブジェクトを渡すさまざまなデリゲートを呼び出します。これらのクラスが完了すると、バッファをスタックにプッシュバックする方法が必要になります。
public class SafeBuffer
{
private static Stack bufferStack;
private static byte[][] buffers;
private byte[] buffer;
private int offset, lenght;
private SafeBuffer(byte[] buffer)
{
this.buffer = buffer;
offset = 0;
lenght = buffer.Length;
}
public static void Init(int count, int blocksize)
{
bufferStack = Stack.Synchronized(new Stack());
buffers = new byte[count][];
for (int i = 0; i < buffers.Length; i++)
buffers[i] = new byte[blocksize];
for (int i = 0; i < buffers.Length; i++)
bufferStack.Push(new SafeBuffer(buffers[i]));
}
public static SafeBuffer Get()
{
return (SafeBuffer)bufferStack.Pop();
}
public void Close()
{
bufferStack.Push(this);
}
public byte[] Buffer
{
get
{
return buffer;
}
}
public int Offset
{
get
{
return offset;
}
set
{
offset = value;
}
}
public int Lenght
{
get
{
return buffer.Length;
}
}
}