2009-04-30 4 views
8

私はコレクションとスレッドを使って遊んでいて、IDisposableパターンを許可してReaderWriterLockSlimの使用を容易にするために人々が作成したすばらしい拡張メソッドを見つけました。ReaderWriterLockSlim拡張メソッドパフォーマンス

しかし、実装の中の何かがパフォーマンスの犠牲者であることがわかったと思います。私は、拡張メソッドが実際にパフォーマンスに影響を及ぼさないと考えているので、実装の何かが原因であると仮定して残されています...作成/収集されたDisposable構造体の量?あなたが見ることができるように実際にパフォーマンスを行いだけlock(モニター)を使用するよりも悪化し拡張子を使用して、

time: 00:00:03.0965242 class: System.Collections.Generic.List`1[System.Int32] 
time: 00:00:11.9194573 class: LockPlay.Program+MonitorList`1[System.Int32] 
time: 00:00:08.9510258 class: LockPlay.Program+RWLSList`1[System.Int32] 
time: 00:00:16.9888435 class: LockPlay.Program+RWLSExtList`1[System.Int32] 
DONE

はここにいくつかのテストコードです:

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Diagnostics; 

namespace LockPlay { 

    static class RWLSExtension { 
     struct Disposable : IDisposable { 
      readonly Action _action; 
      public Disposable(Action action) { 
       _action = action; 
      } 
      public void Dispose() { 
       _action(); 
      } 
     } // end struct 
     public static IDisposable ReadLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterReadLock(); 
      return new Disposable(rwls.ExitReadLock); 
     } 
     public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterUpgradeableReadLock(); 
      return new Disposable(rwls.ExitUpgradeableReadLock); 
     } 
     public static IDisposable WriteLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterWriteLock(); 
      return new Disposable(rwls.ExitWriteLock); 
     } 
    } // end class 

    class Program { 

     class MonitorList<T> : List<T>, IList<T> { 
      object _syncLock = new object(); 
      public MonitorList(IEnumerable<T> collection) : base(collection) { } 
      T IList<T>.this[int index] { 
       get { 
        lock(_syncLock) 
         return base[index]; 
       } 
       set { 
        lock(_syncLock) 
         base[index] = value; 
       } 
      } 
     } // end class 

     class RWLSList<T> : List<T>, IList<T> { 
      ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim(); 
      public RWLSList(IEnumerable<T> collection) : base(collection) { } 
      T IList<T>.this[int index] { 
       get { 
        try { 
         _rwls.EnterReadLock(); 
         return base[index]; 
        } finally { 
         _rwls.ExitReadLock(); 
        } 
       } 
       set { 
        try { 
         _rwls.EnterWriteLock(); 
         base[index] = value; 
        } finally { 
         _rwls.ExitWriteLock(); 
        } 
       } 
      } 
     } // end class 

     class RWLSExtList<T> : List<T>, IList<T> { 
      ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim(); 
      public RWLSExtList(IEnumerable<T> collection) : base(collection) { } 
      T IList<T>.this[int index] { 
       get { 
        using(_rwls.ReadLock()) 
         return base[index]; 
       } 
       set { 
        using(_rwls.WriteLock()) 
         base[index] = value; 
       } 
      } 
     } // end class 

     static void Main(string[] args) { 
      const int ITERATIONS = 100; 
      const int WORK = 10000; 
      const int WRITE_THREADS = 4; 
      const int READ_THREADS = WRITE_THREADS * 3; 

      // create data - first List is for comparison only... not thread safe 
      int[] copy = new int[WORK]; 
      IList<int>[] l = { new List<int>(copy), new MonitorList<int>(copy), new RWLSList<int>(copy), new RWLSExtList<int>(copy) }; 

      // test each list 
      Thread[] writeThreads = new Thread[WRITE_THREADS]; 
      Thread[] readThreads = new Thread[READ_THREADS]; 
      foreach(var list in l) { 
       Stopwatch sw = Stopwatch.StartNew(); 
       for(int k=0; k < ITERATIONS; k++) { 
        for(int i = 0; i < writeThreads.Length; i++) { 
         writeThreads[i] = new Thread(p => { 
          IList<int> il = p as IList<int>; 
          int c = il.Count; 
          for(int j = 0; j < c; j++) { 
           il[j] = j; 
          } 
         }); 
         writeThreads[i].Start(list); 
        } 
        for(int i = 0; i < readThreads.Length; i++) { 
         readThreads[i] = new Thread(p => { 
          IList<int> il = p as IList<int>; 
          int c = il.Count; 
          for(int j = 0; j < c; j++) { 
           int temp = il[j]; 
          } 
         }); 
         readThreads[i].Start(list); 
        } 
        for(int i = 0; i < readThreads.Length; i++) 
         readThreads[i].Join(); 
        for(int i = 0; i < writeThreads.Length; i++) 
         writeThreads[i].Join(); 
       }; 
       sw.Stop(); 
       Console.WriteLine("time: {0} class: {1}", sw.Elapsed, list.GetType()); 
      } 
      Console.WriteLine("DONE"); 
      Console.ReadLine(); 
     } 
    } // end class 
} // end namespace 

はここに典型的な結果です。

答えて

9

数百万の構造体をインスタンス化し、呼び出しの余分なビットのような価格のように見えます。

このサンプルでは、​​ReaderWriterLockSlimが誤用されているとします。この場合、ロックが十分であり、ReaderWriterLockSlimで得られるパフォーマンスのエッジは、これらの概念を説明する価格に比べて無視できる程度ですジュニア開発者。

巨大なは、読み取りと書き込みを実行する際に無視できないほどの時間がかかると、リーダーのライタースタイルのロックが付いています。あなたが主に読み取りベースのシステムを持っているとき、ブーストは最大になります。

ロックが取得されている間にThread.Sleep(1)を挿入して、どれだけ大きな違いがあるかを確認してください。

このベンチマークを参照してください:私は実際には2つのスタイルの違いの多くを気づくことはありません、私はそれを使用して快適に感じるだろう私のベンチマークで

 
Time for Test.SynchronizedList`1[System.Int32] Time Elapsed 12310 ms 
Time for Test.ReaderWriterLockedList`1[System.Int32] Time Elapsed 547 ms 
Time for Test.ManualReaderWriterLockedList`1[System.Int32] Time Elapsed 566 ms 

は、それは人々が処分することを忘れた場合には、いくつかのファイナライザ保護を持っていた提供しました....

using System.Threading; 
using System.Diagnostics; 
using System.Collections.Generic; 
using System; 
using System.Linq; 

namespace Test { 

    static class RWLSExtension { 
     struct Disposable : IDisposable { 
      readonly Action _action; 
      public Disposable(Action action) { 
       _action = action; 
      } 
      public void Dispose() { 
       _action(); 
      } 
     } 

     public static IDisposable ReadLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterReadLock(); 
      return new Disposable(rwls.ExitReadLock); 
     } 
     public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterUpgradeableReadLock(); 
      return new Disposable(rwls.ExitUpgradeableReadLock); 
     } 
     public static IDisposable WriteLock(this ReaderWriterLockSlim rwls) { 
      rwls.EnterWriteLock(); 
      return new Disposable(rwls.ExitWriteLock); 
     } 
    } 

    class SlowList<T> { 

     List<T> baseList = new List<T>(); 

     public void AddRange(IEnumerable<T> items) { 
      baseList.AddRange(items); 
     } 

     public virtual T this[int index] { 
      get { 
       Thread.Sleep(1); 
       return baseList[index]; 
      } 
      set { 
       baseList[index] = value; 
       Thread.Sleep(1); 
      } 
     } 
    } 

    class SynchronizedList<T> : SlowList<T> { 

     object sync = new object(); 

     public override T this[int index] { 
      get { 
       lock (sync) { 
        return base[index]; 
       } 

      } 
      set { 
       lock (sync) { 
        base[index] = value; 
       } 
      } 
     } 
    } 


    class ManualReaderWriterLockedList<T> : SlowList<T> { 

     ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim(); 

     public override T this[int index] { 
      get { 
       T item; 
       try { 
        slimLock.EnterReadLock(); 
        item = base[index]; 
       } finally { 
        slimLock.ExitReadLock(); 
       } 
       return item; 

      } 
      set { 
       try { 
        slimLock.EnterWriteLock(); 
        base[index] = value; 
       } finally { 
        slimLock.ExitWriteLock(); 
       } 
      } 
     } 
    } 

    class ReaderWriterLockedList<T> : SlowList<T> { 

     ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim(); 

     public override T this[int index] { 
      get { 
       using (slimLock.ReadLock()) { 
        return base[index]; 
       } 
      } 
      set { 
       using (slimLock.WriteLock()) { 
        base[index] = value; 
       } 
      } 
     } 
    } 


    class Program { 


     private static void Repeat(int times, int asyncThreads, Action action) { 
      if (asyncThreads > 0) { 

       var threads = new List<Thread>(); 

       for (int i = 0; i < asyncThreads; i++) { 

        int iterations = times/asyncThreads; 
        if (i == 0) { 
         iterations += times % asyncThreads; 
        } 

        Thread thread = new Thread(new ThreadStart(() => Repeat(iterations, 0, action))); 
        thread.Start(); 
        threads.Add(thread); 
       } 

       foreach (var thread in threads) { 
        thread.Join(); 
       } 

      } else { 
       for (int i = 0; i < times; i++) { 
        action(); 
       } 
      } 
     } 

     static void TimeAction(string description, Action func) { 
      var watch = new Stopwatch(); 
      watch.Start(); 
      func(); 
      watch.Stop(); 
      Console.Write(description); 
      Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds); 
     } 

     static void Main(string[] args) { 

      int threadCount = 40; 
      int iterations = 200; 
      int readToWriteRatio = 60; 

      var baseList = Enumerable.Range(0, 10000).ToList(); 

      List<SlowList<int>> lists = new List<SlowList<int>>() { 
       new SynchronizedList<int>() , 
       new ReaderWriterLockedList<int>(), 
       new ManualReaderWriterLockedList<int>() 
      }; 

      foreach (var list in lists) { 
       list.AddRange(baseList); 
      } 


      foreach (var list in lists) { 
       TimeAction("Time for " + list.GetType().ToString(),() => 
       { 
        Repeat(iterations, threadCount,() => 
        { 
         list[100] = 99; 
         for (int i = 0; i < readToWriteRatio; i++) { 
          int ignore = list[i]; 
         } 
        }); 
       }); 
      } 



      Console.WriteLine("DONE"); 
      Console.ReadLine(); 
     } 
    } 
} 
+0

のDisposeを呼び出すことを忘れることはロックを解除し忘れると同一であり、その場合には遅れます非決定論的なファイナライズは、その後のデッドロックの防止にはあまり役立ちません。悪いことに、デッドロックを断続的にしてデバッグすることはほとんど不可能になる可能性があります(可能性があります)。ファイナライザをそのままにして、デバッグ可能なデッドロックを確保してください。 – wekempf

+0

@wekempfを参照してください:http://gist.github.com/104477副作用は、これが少なくともDEBUG –

+0

@ sambo99で動作するためには使い捨て可能なクラスが必要であるということです:私はそこに推論を見ることができますが、まだありません同意する。デッドロックを最終的なデッドロック+最終的なアサートに変えてしまいました。これは、メリットはありません(つまり、デバッグするのは簡単ではありません)。私にはほとんど起こりそうにない間違いがあります(特に、メソッドの名前を変更したり、メソッドを非拡張メソッドにするなど)。 – wekempf

2

私の推測では、(あなたが確認するために、プロファイルする必要があります)、パフォーマンスの低下は、使い捨てのインスタンスを作成するのではないということです(彼らは、という構造体かなり安いはずです)。代わりに、私はそれがアクションデリゲートを作成することだと思います。 Disposable構造体の実装を変更して、Actionデリゲートを作成する代わりにReaderWriterLockSlimのインスタンスを格納することができます。

編集: @ 280Z28の投稿は、それが減速の原因となっているアクション代理人のヒープ割り当てであることを確認します。

7

コードはオブジェクトのオーバーヘッドを避けるために構造体を使用しているようですが、この軽量化を維持するために必要なその他の手順はありません。私はそれがReadLockからの戻り値を囲むと信じています。もしそうなら、構造体全体の利点を否定します。これにより、すべての問題が修正され、IDisposableインターフェイスを経由しないだけでなく、正常に実行されます。

編集:ベンチマークが必要です。これらの結果は、マニュアルメソッド(保護されたコードとインラインでEnter/ExitReadLockEnter/ExitWriteLockを呼び出す)が1.00の時間値を持つように正規化されています。 手動メソッドではないオブジェクトをヒープ上に割り当てるため、元の方法が遅くなります。私はこの問題を解決し、リリースモードでは、手動メソッドと同じ速さで拡張メソッド呼び出しのオーバーヘッドが消えてしまいます。

デバッグビルド:

Manual:    1.00 
Original Extensions: 1.62 
My Extensions:  1.24 

リリースビルド:

Manual:    1.00 
Original Extensions: 1.51 
My Extensions:  1.00 

マイコード:

internal static class RWLSExtension 
{ 
    public static ReadLockHelper ReadLock(this ReaderWriterLockSlim readerWriterLock) 
    { 
     return new ReadLockHelper(readerWriterLock); 
    } 

    public static UpgradeableReadLockHelper UpgradableReadLock(this ReaderWriterLockSlim readerWriterLock) 
    { 
     return new UpgradeableReadLockHelper(readerWriterLock); 
    } 

    public static WriteLockHelper WriteLock(this ReaderWriterLockSlim readerWriterLock) 
    { 
     return new WriteLockHelper(readerWriterLock); 
    } 

    public struct ReadLockHelper : IDisposable 
    { 
     private readonly ReaderWriterLockSlim readerWriterLock; 

     public ReadLockHelper(ReaderWriterLockSlim readerWriterLock) 
     { 
      readerWriterLock.EnterReadLock(); 
      this.readerWriterLock = readerWriterLock; 
     } 

     public void Dispose() 
     { 
      this.readerWriterLock.ExitReadLock(); 
     } 
    } 

    public struct UpgradeableReadLockHelper : IDisposable 
    { 
     private readonly ReaderWriterLockSlim readerWriterLock; 

     public UpgradeableReadLockHelper(ReaderWriterLockSlim readerWriterLock) 
     { 
      readerWriterLock.EnterUpgradeableReadLock(); 
      this.readerWriterLock = readerWriterLock; 
     } 

     public void Dispose() 
     { 
      this.readerWriterLock.ExitUpgradeableReadLock(); 
     } 
    } 

    public struct WriteLockHelper : IDisposable 
    { 
     private readonly ReaderWriterLockSlim readerWriterLock; 

     public WriteLockHelper(ReaderWriterLockSlim readerWriterLock) 
     { 
      readerWriterLock.EnterWriteLock(); 
      this.readerWriterLock = readerWriterLock; 
     } 

     public void Dispose() 
     { 
      this.readerWriterLock.ExitWriteLock(); 
     } 
    } 
} 
+1

コードをプロファイリングしていないし、低速の理由も説明していません。 –

+0

My codeは新しいヒープ割り当てオブジェクトを作成しないため、短いベンチマークで同じ時間であっても、後でメモリマネージャ。 –