2009-04-30 49 views
8

Tôi đã chơi với các bộ sưu tập và luồng và đi qua các phương pháp mở rộng tiện lợi mà mọi người đã tạo ra để dễ dàng sử dụng ReaderWriterLockSlim bằng cách cho phép mẫu IDisposable.ReaderWriterLockSlim Extension Method Performance

Tuy nhiên, tôi tin rằng tôi đã nhận ra rằng điều gì đó trong quá trình triển khai là kẻ giết người hiệu suất. Tôi nhận ra rằng các phương pháp mở rộng không phải là thực sự ảnh hưởng đến hiệu suất, vì vậy tôi còn thừa nhận rằng một cái gì đó trong việc thực hiện là nguyên nhân ... số lượng các cấu trúc dùng một lần tạo ra/thu thập?

Dưới đây là một số mã kiểm tra:

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 

Dưới đây là một kết quả tiêu biểu:

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

Như bạn thấy, sử dụng các phần mở rộng thực sự làm cho việc thực hiện XẤU HƠN hơn là chỉ sử dụng lock (màn hình) .

Trả lời

9

Có vẻ như đó là giá của việc khởi tạo hàng triệu cấu trúc và thêm một chút lời gọi.

Tôi sẽ đi xa để nói rằng ReaderWriterLockSlim đang bị lạm dụng trong mẫu này, khóa là đủ tốt trong trường hợp này và cạnh hiệu suất bạn nhận được với ReaderWriterLockSlim là không đáng kể so với giá giải thích các khái niệm này junior devs.

Bạn nhận được lớn lợi thế với khóa kiểu ghi của trình đọc khi mất thời gian không thể bỏ qua để thực hiện đọc và viết. Sự gia tăng sẽ là lớn nhất khi bạn có một hệ thống dựa trên chủ yếu là đọc.

Hãy thử chèn một Thread.Sleep (1) trong khi các khóa được mua để xem sự khác biệt lớn như thế nào.

Xem điểm chuẩn này:

 
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 

Trong điểm chuẩn của tôi, tôi không thực sự nhận thấy nhiều sự khác biệt giữa hai phong cách, tôi sẽ cảm thấy thoải mái sử dụng nó miễn là nó có một số bảo vệ finalizer trong trường hợp người quên để xử lý ....

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

Quên để gọi hàm Dispose là giống hệt nhau để quên để mở khóa, và trong trường hợp đó trì hoãn và quyết toán không xác định là rất khó để giúp ngăn chặn các khóa chết tiếp theo. Tồi tệ hơn, nó có thể (có thể xảy ra) rằng nó sẽ làm cho các khóa chết liên tục và gần như không thể gỡ lỗi. Để finalizer ra và đảm bảo chúng tôi có được một bế tắc có thể gỡ rối. – wekempf

+0

@wekempf thấy: http://gist.github.com/104477 tác dụng phụ là nhu cầu dùng một lần phải là một lớp học để làm việc này ít nhất là trong DEBUG –

+0

@ sambo99: Tôi có thể thấy lý do ở đó, nhưng vẫn không đồng ý. Tốt nhất bạn đã biến khóa chết thành khóa chết + xác nhận cuối cùng, không có lợi ích bổ sung (tức là không dễ gỡ lỗi hơn). Tất cả cho một sai lầm mà dường như HIGHLY dường như không xảy ra với tôi (đặc biệt là nếu bạn hoặc thay đổi tên của các phương pháp hoặc làm cho họ không mở rộng phương pháp). – wekempf

2

Đoán của tôi (bạn cần cấu hình để xác minh) là hiệu suất không phải là tạo trường hợp dùng một lần (chúng phải khá rẻ, là cấu trúc). Thay vào đó tôi hy vọng nó là từ việc tạo ra các đại biểu Action. Bạn có thể thử thay đổi việc thực hiện cấu trúc dùng một lần của bạn để lưu trữ cá thể của ReaderWriterLockSlim thay vì tạo một ủy nhiệm Hành động.

Chỉnh sửa: @ 280Z28 của bài đăng xác nhận rằng đó là phân bổ heap của đại biểu Hành động gây ra sự chậm lại.

7

Mã này dường như sử dụng cấu trúc để tránh chi phí tạo đối tượng, nhưng không thực hiện các bước cần thiết khác để giữ trọng lượng nhẹ này. Tôi tin rằng nó sẽ trả về giá trị trả về từ ReadLock và nếu như vậy phủ nhận toàn bộ lợi thế của cấu trúc. Điều này sẽ khắc phục tất cả các vấn đề và thực hiện cũng như không thông qua giao diện IDisposable.

Chỉnh sửa: Điểm chuẩn được yêu cầu.Các kết quả này được chuẩn hóa để phương pháp thủ công (gọi Enter/ExitReadLockEnter/ExitWriteLock nội tuyến với mã được bảo vệ) có giá trị thời gian là 1,00. Phương pháp ban đầu là chậm vì nó phân bổ các đối tượng trên heap mà phương pháp thủ công không. Tôi đã khắc phục sự cố này và trong chế độ phát hành, ngay cả khi phương thức tiện ích mở rộng gọi trên đầu sẽ biến mất để nó giống hệt như phương pháp thủ công.

gỡ lỗi tích xây dựng:

Manual:    1.00 
Original Extensions: 1.62 
My Extensions:  1.24 

phát hành xây dựng:

Manual:    1.00 
Original Extensions: 1.51 
My Extensions:  1.00 

Mã của tôi:

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

Bạn thậm chí không hồ sơ mã của bạn, cũng không giải thích tại sao mã của nó chậm –

+0

Mã của tôi không tạo bất kỳ đối tượng phân bổ heap mới nào, vì vậy ngay cả khi thời gian giống hệt nhau trên một tiêu chuẩn ngắn, phương pháp này đặt ít căng thẳng hơn quản lý bộ nhớ sau. –