web-dev-qa-db-ja.com

ReaderWriterLockSlimは、単純なロックよりも優れているのはいつですか?

私はこのコードで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倍の頻度で読み取りを行っても、パフォーマンスは依然として(ほぼ)同じです。

ここで何か間違ったことをしていますか?

敬具。

72
vtortola

あなたの例では、スリープは一般的に競合がないことを意味します。競合しないロックは非常に高速です。このためには、contendedロックが必要です。その競合にwritesがある場合、それらはほぼ同じである必要があります(lockはもっと速いかもしれません)-しかし、そうである場合ほとんど読み取り(書き込み競合はめったにありません)で、ReaderWriterLockSlimロックがlockをアウトパフォームすることを期待します。

個人的には、ここで参照スワップを使用する別の戦略を好みます-したがって、読み取りは常にチェック/ロック/などせずに読み取ることができます。書き込みは、clonedコピーに変更を加え、Interlocked.CompareExchange参照を交換します(別のスレッドが一時的に参照を変更した場合に変更を再適用します)。

96
Marc Gravell

私自身のテストでは、ReaderWriterLockSlimが通常のlockに比べて約5倍のオーバーヘッドがあることを示しています。つまり、RWLSが単純な古いロックよりも優れているということは、一般的に次の条件が発生することを意味します。

  • リーダーの数はライターを大きく上回っています。
  • ロックは、追加のオーバーヘッドを克服するために十分長く保持する必要があります。

ほとんどの実際のアプリケーションでは、これらの2つの条件は、追加のオーバーヘッドを克服するには不十分です。特にコードでは、ロックは非常に短い期間保持されるため、ロックのオーバーヘッドがおそらく支配的な要因になります。それらを移動する場合はThread.Sleepロック内で呼び出すと、おそらく異なる結果が得られます。

21
Brian Gideon

このプログラムに競合はありません。 GetメソッドとAddメソッドは数ナノ秒で実行されます。複数のスレッドが正確にそれらのメソッドにヒットする可能性は、ごくわずかです。

Thread.Sleep(1)呼び出しを追加し、スレッドからスリープを解除して、違いを確認します。

14
Hans Passant

Edit 2ReadThreadおよびWriteThreadから_Thread.Sleep_呼び出しを削除するだけで、Lockedを見ましたRWLockedを上回る。 ハンス ここで頭に釘を打ちます。メソッドが速すぎて、競合が発生しません。 Thread.Sleep(1)GetおよびAddメソッドのLockedおよびRWLockedに追加した場合(および1つの書き込みスレッドに対して4つの読み取りスレッドを使用した場合)、RWLockedLockedからズボンを破りました。


編集:OK、最初にこの回答を投稿したときに実際に思考だったら、少なくともwhyに_Thread.Sleep_呼び出しを入れたことに気づきました:書き込みよりも頻繁に発生する読み取りのシナリオを再現しようとしていました。これはそれを行う正しい方法ではありません。代わりに、AddメソッドとGetメソッドに余分なオーバーヘッドを導入して、競合の可能性を高め( ハンス推奨 )、書き込みスレッドよりも多くの読み取りスレッドを作成します(書き込みよりも頻繁に読み取りを行う)、ReadThreadおよびWriteThread(実際にはreduce競合から_Thread.Sleep_呼び出しを削除し、反対を達成するあなたが望むものの)。


私はあなたがこれまでにしたことを気に入っています。しかし、ここに私がすぐに目にするいくつかの問題があります:

  1. _Thread.Sleep_が呼び出される理由これらは、実行時間を一定量だけ膨らませているだけで、パフォーマンスの結果を人為的に収束させます。
  2. また、Threadによって測定されるコードに、新しいStopwatchオブジェクトの作成を含めません。それは簡単なオブジェクトを作成することではありません。

上記の2つの問題に対処した後に大きな違いが見られるかどうかはわかりません。しかし、議論を続ける前にそれらに対処すべきだと思います。

10
Dan Tao

実行に時間がかかるコードの一部をロックする場合、単純なロックよりもReaderWriterLockSlimを使用するとパフォーマンスが向上します。この場合、読者は並行して作業できます。 ReaderWriterLockSlimを取得するには、単純なMonitorを入力するよりも時間がかかります。 ReaderWriterLockTiny実装をチェックして、単純なロックステートメントよりも高速で、リーダーライター機能を提供するリーダーライターロックを確認してください。 http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net /

8
i255

この記事をご覧ください: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

あなたの睡眠はおそらくあなたのロック/ロック解除を統計的に取るに足らないほど長くするでしょう。

3

ncontestedロックは取得にマイクロ秒のオーダーを要するため、Sleepの呼び出しにより実行時間が短縮されます。

3
MSN

マルチコアハードウェア(または少なくとも計画された運用環境と同じ)がない限り、ここで現実的なテストを行うことはできません。

より賢明なテストは、短い遅延insideロックを置くことにより、ロックされた操作の寿命を延ばすことです。そうすれば、ReaderWriterLockSlimを使用して追加された並列処理と、基本的なlock()によって暗示されるシリアル化を実際に対比できるはずです。

現在、ロックされている操作にかかる時間は、ロックの外側で発生するスリープコールによって生成されるノイズで失われています。いずれの場合でも、合計時間は主に睡眠関連です。

実際のアプリは、読み取りと書き込みの数が等しいと確信していますか? ReaderWriterLockSlimは、多くの読者と比較的まれな作家がいる場合には本当に優れています。 1つのライタースレッドと3つのリーダースレッドはReaderWriterLockSlimの利点を実証する必要がありますが、いずれにしても、テストは実際のアクセスパターンと一致する必要があります。

3
Steve Townsend

これは、リーダースレッドとライタースレッドでのスリープのためです。
あなたの読み取りスレッドのスリープ時間は500tims 50msで、これは25000ですほとんどの時間はスリープしています

2
Itay Karo

ReaderWriterLockSlimは、単純なロックよりも優れているのはいつですか?

書き込みよりも読み取りが大幅に多い場合。

0
Illidan