私はこのコードでReaderWriterLockの非常にばかげたベンチマークを行っています。
class Program
{
static void Main()
{
ISynchro[] test = { new Locked(), new RWLocked() };
Stopwatch sw = new Stopwatch();
foreach ( var isynchro in test )
{
sw.Reset();
sw.Start();
Thread w1 = new Thread( new ParameterizedThreadStart( WriteThread ) );
w1.Start( isynchro );
Thread w2 = new Thread( new ParameterizedThreadStart( WriteThread ) );
w2.Start( isynchro );
Thread r1 = new Thread( new ParameterizedThreadStart( ReadThread ) );
r1.Start( isynchro );
Thread r2 = new Thread( new ParameterizedThreadStart( ReadThread ) );
r2.Start( isynchro );
w1.Join();
w2.Join();
r1.Join();
r2.Join();
sw.Stop();
Console.WriteLine( isynchro.ToString() + ": " + sw.ElapsedMilliseconds.ToString() + "ms." );
}
Console.WriteLine( "End" );
Console.ReadKey( true );
}
static void ReadThread(Object o)
{
ISynchro synchro = (ISynchro)o;
for ( int i = 0; i < 500; i++ )
{
Int32? value = synchro.Get( i );
Thread.Sleep( 50 );
}
}
static void WriteThread( Object o )
{
ISynchro synchro = (ISynchro)o;
for ( int i = 0; i < 125; i++ )
{
synchro.Add( i );
Thread.Sleep( 200 );
}
}
}
interface ISynchro
{
void Add( Int32 value );
Int32? Get( Int32 index );
}
class Locked:List<Int32>, ISynchro
{
readonly Object locker = new object();
#region ISynchro Members
public new void Add( int value )
{
lock ( locker )
base.Add( value );
}
public int? Get( int index )
{
lock ( locker )
{
if ( this.Count <= index )
return null;
return this[ index ];
}
}
#endregion
public override string ToString()
{
return "Locked";
}
}
class RWLocked : List<Int32>, ISynchro
{
ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
#region ISynchro Members
public new void Add( int value )
{
try
{
locker.EnterWriteLock();
base.Add( value );
}
finally
{
locker.ExitWriteLock();
}
}
public int? Get( int index )
{
try
{
locker.EnterReadLock();
if ( this.Count <= index )
return null;
return this[ index ];
}
finally
{
locker.ExitReadLock();
}
}
#endregion
public override string ToString()
{
return "RW Locked";
}
}
しかし、私は両方ともほぼ同じ方法でパフォーマンスを発揮します:
Locked: 25003ms.
RW Locked: 25002ms.
End
書き込みの20倍の頻度で読み取りを行っても、パフォーマンスは依然として(ほぼ)同じです。
ここで何か間違ったことをしていますか?
敬具。
あなたの例では、スリープは一般的に競合がないことを意味します。競合しないロックは非常に高速です。このためには、contendedロックが必要です。その競合にwritesがある場合、それらはほぼ同じである必要があります(lock
はもっと速いかもしれません)-しかし、そうである場合ほとんど読み取り(書き込み競合はめったにありません)で、ReaderWriterLockSlim
ロックがlock
をアウトパフォームすることを期待します。
個人的には、ここで参照スワップを使用する別の戦略を好みます-したがって、読み取りは常にチェック/ロック/などせずに読み取ることができます。書き込みは、clonedコピーに変更を加え、Interlocked.CompareExchange
参照を交換します(別のスレッドが一時的に参照を変更した場合に変更を再適用します)。
私自身のテストでは、ReaderWriterLockSlim
が通常のlock
に比べて約5倍のオーバーヘッドがあることを示しています。つまり、RWLSが単純な古いロックよりも優れているということは、一般的に次の条件が発生することを意味します。
ほとんどの実際のアプリケーションでは、これらの2つの条件は、追加のオーバーヘッドを克服するには不十分です。特にコードでは、ロックは非常に短い期間保持されるため、ロックのオーバーヘッドがおそらく支配的な要因になります。それらを移動する場合はThread.Sleep
ロック内で呼び出すと、おそらく異なる結果が得られます。
このプログラムに競合はありません。 GetメソッドとAddメソッドは数ナノ秒で実行されます。複数のスレッドが正確にそれらのメソッドにヒットする可能性は、ごくわずかです。
Thread.Sleep(1)呼び出しを追加し、スレッドからスリープを解除して、違いを確認します。
Edit 2:ReadThread
およびWriteThread
から_Thread.Sleep
_呼び出しを削除するだけで、Locked
を見ましたRWLocked
を上回る。 ハンス ここで頭に釘を打ちます。メソッドが速すぎて、競合が発生しません。 Thread.Sleep(1)
をGet
およびAdd
メソッドのLocked
およびRWLocked
に追加した場合(および1つの書き込みスレッドに対して4つの読み取りスレッドを使用した場合)、RWLocked
はLocked
からズボンを破りました。
編集:OK、最初にこの回答を投稿したときに実際に思考だったら、少なくともwhyに_Thread.Sleep
_呼び出しを入れたことに気づきました:書き込みよりも頻繁に発生する読み取りのシナリオを再現しようとしていました。これはそれを行う正しい方法ではありません。代わりに、Add
メソッドとGet
メソッドに余分なオーバーヘッドを導入して、競合の可能性を高め( ハンス推奨 )、書き込みスレッドよりも多くの読み取りスレッドを作成します(書き込みよりも頻繁に読み取りを行う)、ReadThread
およびWriteThread
(実際にはreduce競合から_Thread.Sleep
_呼び出しを削除し、反対を達成するあなたが望むものの)。
私はあなたがこれまでにしたことを気に入っています。しかし、ここに私がすぐに目にするいくつかの問題があります:
Thread.Sleep
_が呼び出される理由これらは、実行時間を一定量だけ膨らませているだけで、パフォーマンスの結果を人為的に収束させます。Thread
によって測定されるコードに、新しいStopwatch
オブジェクトの作成を含めません。それは簡単なオブジェクトを作成することではありません。上記の2つの問題に対処した後に大きな違いが見られるかどうかはわかりません。しかし、議論を続ける前にそれらに対処すべきだと思います。
実行に時間がかかるコードの一部をロックする場合、単純なロックよりもReaderWriterLockSlim
を使用するとパフォーマンスが向上します。この場合、読者は並行して作業できます。 ReaderWriterLockSlim
を取得するには、単純なMonitor
を入力するよりも時間がかかります。 ReaderWriterLockTiny
実装をチェックして、単純なロックステートメントよりも高速で、リーダーライター機能を提供するリーダーライターロックを確認してください。 http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net /
あなたの睡眠はおそらくあなたのロック/ロック解除を統計的に取るに足らないほど長くするでしょう。
ncontestedロックは取得にマイクロ秒のオーダーを要するため、Sleep
の呼び出しにより実行時間が短縮されます。
マルチコアハードウェア(または少なくとも計画された運用環境と同じ)がない限り、ここで現実的なテストを行うことはできません。
より賢明なテストは、短い遅延insideロックを置くことにより、ロックされた操作の寿命を延ばすことです。そうすれば、ReaderWriterLockSlim
を使用して追加された並列処理と、基本的なlock()
によって暗示されるシリアル化を実際に対比できるはずです。
現在、ロックされている操作にかかる時間は、ロックの外側で発生するスリープコールによって生成されるノイズで失われています。いずれの場合でも、合計時間は主に睡眠関連です。
実際のアプリは、読み取りと書き込みの数が等しいと確信していますか? ReaderWriterLockSlim
は、多くの読者と比較的まれな作家がいる場合には本当に優れています。 1つのライタースレッドと3つのリーダースレッドはReaderWriterLockSlim
の利点を実証する必要がありますが、いずれにしても、テストは実際のアクセスパターンと一致する必要があります。
これは、リーダースレッドとライタースレッドでのスリープのためです。
あなたの読み取りスレッドのスリープ時間は500tims 50msで、これは25000ですほとんどの時間はスリープしています
ReaderWriterLockSlimは、単純なロックよりも優れているのはいつですか?
書き込みよりも読み取りが大幅に多い場合。