2011-01-27 35 views
7

Tôi có một Hàng đợi trên đó hoạt động Enqueue sẽ được thực hiện bởi một sợi và Dequeue sẽ được thực hiện bởi một sợi khác. Không cần phải nói, tôi đã phải thực hiện một số chủ đề an toàn cho nó.là Queue.Synchronized nhanh hơn sử dụng Lock()?

Lần đầu tiên tôi thử sử dụng khóa trên hàng đợi trước mỗi Enqueue/Dequeue vì nó cung cấp khả năng kiểm soát tốt hơn cho cơ chế khóa. Nó hoạt động tốt, nhưng tâm trí tò mò của tôi đã dẫn tôi đến kiểm tra thêm.

Sau đó, tôi đã thử sử dụng trình bao bọc Queue.Synchronized giữ mọi thứ khác giống nhau. Bây giờ, tôi không chắc chắn nếu nó đúng, nhưng hiệu suất dường như một chút chút nhanh hơn với cách tiếp cận này.

Bạn có nghĩ rằng, thực sự có sự khác biệt về hiệu suất giữa hai yếu tố này, hoặc tôi chỉ tưởng tượng ra những thứ ở đây ..? :)

+3

Chỉ cần lấy ra một khóa bằng cách sử dụng một trong hai phương pháp là điên rồ nhanh chóng. Các hit hiệu suất đến từ bao nhiêu ganh đua có cho các ổ khóa. – serg10

Trả lời

16

Khi yêu cầu Queue.Synchonized bạn nhận được một số SynchronizedQueue đổi lại sử dụng số lock rất tối thiểu xung quanh các cuộc gọi đến EnqueueDequeue trên hàng đợi bên trong. Do đó, hiệu suất phải giống như việc sử dụng số Queue và tự quản lý khóa cho EnqueueDequeue với số lock của riêng bạn.

Bạn thực sự đang tưởng tượng mọi thứ - chúng phải giống nhau.

Cập nhật

Có thực sự là một thực tế rằng khi sử dụng một SynchronizedQueue bạn đang thêm một lớp về mình như bạn phải đi qua các phương pháp wrapper để có được vào hàng đợi bên trong mà nó đang quản lý. Nếu bất cứ điều gì này nên làm chậm những thứ xuống rất phân đoạn như bạn đã có một khung phụ trên ngăn xếp mà cần phải được quản lý cho mỗi cuộc gọi. Thiên Chúa biết nếu trong lót hủy bỏ điều này mặc dù. Bất cứ điều gì - đó là tối thiểu.

Cập nhật 2

Bây giờ tôi đã làm chuẩn này, và như dự đoán trong bản cập nhật trước đây của tôi:

"Queue.Synchronized" là chậm hơn so với "Queue + khóa"

Tôi thực hiện một thử nghiệm đơn luồng vì cả hai đều sử dụng kỹ thuật khóa tương tự (ví dụ lock) để kiểm tra chi phí tinh khiết trong một "đường thẳng" có vẻ hợp lý.

benchmark của tôi tạo ra kết quả như sau cho một phát hành xây dựng:

Iterations  :10,000,000 

Queue+Lock  :539.14ms 
Queue+Lock  :540.55ms 
Queue+Lock  :539.46ms 
Queue+Lock  :540.46ms 
Queue+Lock  :539.75ms 
SynchonizedQueue:578.67ms 
SynchonizedQueue:585.04ms 
SynchonizedQueue:580.22ms 
SynchonizedQueue:578.35ms 
SynchonizedQueue:578.57ms 

Sử dụng đoạn mã sau:

private readonly object _syncObj = new object(); 

[Test] 
public object measure_queue_locking_performance() 
{ 
    const int TestIterations = 5; 
    const int Iterations = (10 * 1000 * 1000); 

    Action<string, Action> time = (name, test) => 
    { 
     for (int i = 0; i < TestIterations; i++) 
     { 
      TimeSpan elapsed = TimeTest(test, Iterations); 
      Console.WriteLine("{0}:{1:F2}ms", name, elapsed.TotalMilliseconds); 
     } 
    }; 

    object itemOut, itemIn = new object(); 
    Queue queue = new Queue(); 
    Queue syncQueue = Queue.Synchronized(queue); 

    Action test1 =() => 
    { 
     lock (_syncObj) queue.Enqueue(itemIn); 
     lock (_syncObj) itemOut = queue.Dequeue(); 
    }; 

    Action test2 =() => 
    { 
     syncQueue.Enqueue(itemIn); 
     itemOut = syncQueue.Dequeue(); 
    }; 

    Console.WriteLine("Iterations:{0:0,0}\r\n", Iterations); 
    time("Queue+Lock", test1); 
    time("SynchonizedQueue", test2); 

    return itemOut; 
} 

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")] 
private static TimeSpan TimeTest(Action action, int iterations) 
{ 
    Action gc =() => 
    { 
     GC.Collect(); 
     GC.WaitForFullGCComplete(); 
    }; 

    Action empty =() => { }; 

    Stopwatch stopwatch1 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) 
    { 
     empty(); 
    } 

    TimeSpan loopElapsed = stopwatch1.Elapsed; 

    gc(); 
    action(); //JIT 
    action(); //Optimize 

    Stopwatch stopwatch2 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) action(); 

    gc(); 

    TimeSpan testElapsed = stopwatch2.Elapsed; 

    return (testElapsed - loopElapsed); 
} 
+0

Không nếu OP không đủ mạnh trong việc giải phóng khóa, hoặc quá háo hức để có được nó. – jason

+0

@ Jason Tôi đang giả định rằng OP đang sử dụng khóa tối thiểu xung quanh các cuộc gọi 'Enqueue' và' Dequeue', nhưng tôi thấy quan điểm của bạn. Tôi đã làm rõ một chút trong câu trả lời của tôi. –

+0

cảm ơn vì đã đầu tư thời gian và nỗ lực trong việc nhận được câu trả lời có thẩm quyền cho vấn đề này. Kudos cho bạn. –

6

Chúng tôi không thể trả lời câu hỏi này cho bạn. Chỉ bạn mới có thể tự mình trả lời bằng cách nhận hồ sơ và kiểm tra cả hai trường hợp (Queue.Synchronized so với Lock) về dữ liệu trong thế giới thực từ ứng dụng của bạn. Nó thậm chí có thể không phải là một nút cổ chai trong ứng dụng của bạn.

Điều đó nói rằng, có thể bạn chỉ nên sử dụng ConcurrentQueue.

+0

Đáng buồn thay, chúng tôi vẫn ở .net 3.5 .. –

+1

@Danish: Kiểm tra [Rx] (http://msdn.microsoft.com/en-us/devlabs/ee794896). Nó có một backport của 'ConcurrentQueue ' cho .NET 3.5. –

+0

@Dan Có phải phiên bản của 'ConcurrentQueue ' thực sự thường nhanh hơn? Tôi đã thấy lời khuyên đó một vài lần, nhưng không có con số thực tế thực tế. Bất kỳ số liệu? –

0
  1. Queue.Synchronize Wraps một hàng đợi mới đồng bộ trong khi Khóa Queue. SyncRoot cung cấp cho một đối tượng truy cập vào hàng đợi được đồng bộ hóa theo cách như vậy, theo cách này bạn có thể đảm bảo an toàn luồng trong hàng đợi trong khi sử dụng đồng thời các phép toán Enqueue và Dequeue aneously bằng cách sử dụng Threads.
Các vấn đề liên quan