2013-05-06 23 views
17

Tôi tự hỏi làm thế nào một đoạn mã bị khóa có thể làm chậm mã của tôi mặc dù mã không bao giờ được thực hiện. Dưới đây là ví dụ bên dưới:Làm thế nào để tránh bị chậm lại do mã bị khóa?

public void Test_PerformanceUnit() 
{ 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    Random r = new Random(); 
    for (int i = 0; i < 10000; i++) 
    { 
     testRand(r); 
    } 
    sw.Stop(); 
    Console.WriteLine(sw.ElapsedTicks); 
} 

public object testRand(Random r) 
{ 
    if (r.Next(1) > 10) 
    { 
     lock(this) { 
      return null; 
     } 
    } 
    return r; 
} 

Mã này chạy trong ~ 1300ms trên máy của tôi. Nếu chúng ta loại bỏ khối khóa (nhưng giữ cơ thể của nó), chúng ta nhận được 750ms. Gần như gấp đôi, mặc dù mã không bao giờ chạy!

Tất nhiên mã này không làm gì cả. Tôi nhận thấy nó trong khi thêm một số khởi tạo lười biếng trong một lớp nơi mã kiểm tra nếu đối tượng được khởi tạo và nếu không khởi tạo nó. Vấn đề là việc khởi tạo bị khóa và làm chậm mọi thứ ngay cả sau cuộc gọi đầu tiên.

Câu hỏi của tôi là:

  1. Tại sao điều này xảy ra?
  2. Làm thế nào để tránh suy thoái
+0

Trừ khi bạn có ý định sử dụng 'khóa' mạnh mẽ - tôi sẽ không thực sự lo lắng về điều đó. – James

+4

Tôi nhận được kết quả tương tự, nhưng đánh dấu là 100 * nano * giây. Cả hai lần chạy phải mất ~ 0ms (nghĩa là nếu bạn in 'sw.ElapseMilliseconds'.)" Chậm "(trong số ~ 0.00006s) có thể do thực tế là' khóa' bao gồm khối 'try/finally' có thể đang được thiết lập khi phương thức được gọi. Thử đặt nội dung 'testRand' vào chính vòng lặp; bạn sẽ thấy gần như * không * chậm lại tại thời điểm đó. – dlev

+0

Bạn đã thử đánh dấu phương thức với 'AggressiveInline' chưa? Có lẽ mã khóa làm cho phương pháp quá lớn đối với nội tuyến bình thường. Các JITter .net inlines sử dụng một heuristic khá câm dựa trên kích thước của mã IL. – CodesInChaos

Trả lời

9

Về lý do tại sao nó xảy ra, nó đã được thảo luận trong các ý kiến: đó là do sự khởi của try ... finally tạo ra bởi các lock.


Và để tránh suy thoái này, bạn có thể trích xuất các tính năng khóa đến một phương pháp mới, do đó cơ chế khóa sẽ chỉ được khởi tạo nếu phương pháp này là thực sự gọi.

Tôi đã thử nó với mã này rất đơn giản:

public object testRand(Random r) 
{ 
    if (r.Next(1) > 10) 
    { 
     return LockingFeature(); 
    } 
    return r; 
} 

private object LockingFeature() 
{ 
    lock (_lock) 
    { 
     return null; 
    } 
} 

Và đây là thời gian của tôi (trong ve):

your code, no lock : ~500 
your code, with lock : ~1200 
my code    : ~500 

EDIT: mã kiểm tra của tôi (chạy chậm hơn một chút so với mã không có khóa) thực sự là trên các phương thức tĩnh, có vẻ như khi mã được chạy "bên trong" một đối tượng, các timings là như nhau. Tôi cố định thời gian theo đó.

+0

Cảm ơn câu trả lời, đây chính xác là những gì tôi đang tìm kiếm. Trong thử nghiệm của tôi, giải pháp của bạn chạy nhanh hơn 'lock' nội tuyến nhưng chậm hơn so với chỉ có' ​​return null'. Tôi đã định nghĩa phương thức 'LockingFeature' là' virtual' để tránh mã nội tuyến và tôi nhận được 100% hiệu suất của mình. – pieroxy

+0

@pieroxy - Một điều nữa về thử nghiệm đầu tiên của bạn là phiên bản 'testRand()' với khóa cũng mất nhiều thời gian hơn đối với JIT. Vì vậy, bạn có thể lấy nó ra khỏi phương trình bằng cách chỉ thực hiện một cuộc gọi đến 'testRand()' trước khi 'Stopwatch' bắt đầu (như một cách để làm ấm trình biên dịch JIT, vì vậy để nói). Điều này thu hẹp khoảng cách đáng kể. Tuy nhiên, mã của Zonko là một cách khá khéo léo để giải quyết vấn đề này. –

Các vấn đề liên quan