2010-11-02 26 views
14

Tôi nhận ra đây là cách quá xa vào khu vực tối ưu hóa vi mô, nhưng tôi tò mò muốn hiểu tại sao Cuộc gọi đến DateTime.Now và DateTime.UtcNow quá "đắt tiền" . Tôi có một chương trình mẫu chạy một vài kịch bản của làm một số "công việc" (thêm vào một truy cập) và cố gắng để làm điều này trong 1 giây. Tôi có một số cách tiếp cận làm cho nó làm công việc cho một số lượng hạn chế thời gian. Các ví dụ cho thấy DateTime.Now và DateTime.UtcNow chậm hơn đáng kể so với Environment.TickCount, nhưng thậm chí chậm hơn so với chỉ để cho một luồng ngủ riêng biệt trong 1 giây và sau đó thiết lập một giá trị để cho biết luồng công nhân dừng lại.Tại sao DateTime.Now DateTime.UtcBây giờ quá chậm/tốn kém

Vì vậy, câu hỏi của tôi là những:

  • Tôi biết rằng UtcNow là nhanh hơn bởi vì nó không có thông tin múi giờ, tại sao nó vẫn còn quá chậm hơn nhiều so với TickCount?
  • Tại sao đọc boolean nhanh hơn int?
  • Cách lý tưởng để đối phó với các loại tình huống này là nơi bạn cần cho phép một thứ gì đó chạy trong một khoảng thời gian hạn chế, nhưng bạn không muốn lãng phí thời gian kiểm tra thời gian hơn là thực sự làm công việc?

Hãy tha thứ tính cách rườm rà của ví dụ:

class Program 
{ 
    private static volatile bool done = false; 
    private static volatile int doneInt = 0; 
    private static UInt64 doneLong = 0; 

    private static ManualResetEvent readyEvent = new ManualResetEvent(false); 

    static void Main(string[] args) 
    { 
     MethodA_PrecalcEndTime(); 
     MethodB_CalcEndTimeEachTime(); 
     MethodC_PrecalcEndTimeUsingUtcNow(); 

     MethodD_EnvironmentTickCount(); 

     MethodX_SeperateThreadBool(); 
     MethodY_SeperateThreadInt(); 
     MethodZ_SeperateThreadLong(); 

     Console.WriteLine("Done..."); 
     Console.ReadLine(); 
    } 

    private static void MethodA_PrecalcEndTime() 
    { 
     int cnt = 0; 
     var doneTime = DateTime.Now.AddSeconds(1); 
     var startDT = DateTime.Now; 
     while (DateTime.Now <= doneTime) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void MethodB_CalcEndTimeEachTime() 
    { 
     int cnt = 0; 
     var startDT = DateTime.Now; 
     while (DateTime.Now <= startDT.AddSeconds(1)) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void MethodC_PrecalcEndTimeUsingUtcNow() 
    { 
     int cnt = 0; 
     var doneTime = DateTime.UtcNow.AddSeconds(1); 
     var startDT = DateTime.Now; 
     while (DateTime.UtcNow <= doneTime) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 


    private static void MethodD_EnvironmentTickCount() 
    { 
     int cnt = 0; 
     int doneTick = Environment.TickCount + 1000; // <-- should be sane near where the counter clocks... 
     var startDT = DateTime.Now; 
     while (Environment.TickCount <= doneTick) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void MethodX_SeperateThreadBool() 
    { 
     readyEvent.Reset(); 
     Thread counter = new Thread(CountBool); 
     Thread waiter = new Thread(WaitBool); 
     counter.Start(); 
     waiter.Start(); 
     waiter.Join(); 
     counter.Join(); 
    } 

    private static void CountBool() 
    { 
     int cnt = 0; 
     readyEvent.WaitOne(); 
     var startDT = DateTime.Now; 
     while (!done) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void WaitBool() 
    { 
     readyEvent.Set(); 
     Thread.Sleep(TimeSpan.FromSeconds(1)); 
     done = true; 
    } 

    private static void MethodY_SeperateThreadInt() 
    { 
     readyEvent.Reset(); 
     Thread counter = new Thread(CountInt); 
     Thread waiter = new Thread(WaitInt); 
     counter.Start(); 
     waiter.Start(); 
     waiter.Join(); 
     counter.Join(); 
    } 

    private static void CountInt() 
    { 
     int cnt = 0; 
     readyEvent.WaitOne(); 
     var startDT = DateTime.Now; 
     while (doneInt<1) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void WaitInt() 
    { 
     readyEvent.Set(); 
     Thread.Sleep(TimeSpan.FromSeconds(1)); 
     doneInt = 1; 
    } 

    private static void MethodZ_SeperateThreadLong() 
    { 
     readyEvent.Reset(); 
     Thread counter = new Thread(CountLong); 
     Thread waiter = new Thread(WaitLong); 
     counter.Start(); 
     waiter.Start(); 
     waiter.Join(); 
     counter.Join(); 
    } 

    private static void CountLong() 
    { 
     int cnt = 0; 
     readyEvent.WaitOne(); 
     var startDT = DateTime.Now; 
     while (doneLong < 1) 
     { 
      cnt++; 
     } 
     var endDT = DateTime.Now; 
     Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); 
    } 

    private static void WaitLong() 
    { 
     readyEvent.Set(); 
     Thread.Sleep(TimeSpan.FromSeconds(1)); 
     doneLong = 1; 
    } 

} 

Trả lời

17

TickCount chỉ đọc một bộ đếm không ngừng tăng lên. Nó chỉ là điều đơn giản nhất bạn có thể làm.

DateTime.UtcNow cần truy vấn thời gian hệ thống - và đừng quên rằng trong khi TickCount là không biết gì về những thứ như người dùng thay đổi đồng hồ hoặc NTP, UtcNow phải tính đến điều này.

Bây giờ bạn đã thể hiện mối quan tâm về hiệu suất - nhưng trong các ví dụ bạn đã đưa ra, tất cả những gì bạn đang làm là tăng bộ đếm. Tôi hy vọng rằng trong mã thực sự của bạn , bạn sẽ làm nhiều việc hơn thế. Nếu bạn đang thực hiện số lượng đáng kể số lượng công việc, có khả năng sẽ làm giảm thời gian thực hiện bởi UtcNow. Trước khi làm bất cứ điều gì khác, bạn nên đo lường điều đó để tìm hiểu xem bạn đang thực sự cố gắng giải quyết một vấn đề không tồn tại.

Nếu bạn làm cần phải cải thiện điều này, thì:

  • Bạn có thể sử dụng một bộ đếm thời gian chứ không phải là tạo ra một chủ đề mới một cách rõ ràng. Có nhiều loại bộ đếm thời gian khác nhau trong khung công tác và không biết chính xác tình huống của bạn, tôi không thể tư vấn cho việc sử dụng nào hợp lý nhất - nhưng nó giống như một giải pháp tốt hơn là bắt đầu một chuỗi.
  • Bạn có thể đo một vài lần lặp lại tác vụ của mình, sau đó đoán số lượng thực sự sẽ được yêu cầu. Bạn có thể muốn sau đó thực hiện một nửa số lần lặp lại nhiều lần, lấy số lượng đã mất bao lâu, sau đó điều chỉnh số chu kỳ còn lại cho phù hợp. Tất nhiên, điều này không hoạt động nếu thời gian thực hiện cho mỗi lần lặp lại có thể thay đổi một cách dữ dội.
+0

Cảm ơn Jon. Tôi sẽ nhìn vào giờ. Tôi nhận ra "công việc" trong ví dụ của tôi là không thực tế. Tuy nhiên tôi đã tò mò về nơi tác động lớn đến từ đâu. –

+1

@ My Other Me: Về cơ bản, so với công việc tăng lượt truy cập, gần như mọi công việc sẽ được tính là một tác động lớn :) –

15

FWIW đây là một số mã mà NLog sử dụng để nhận dấu thời gian cho mỗi thông điệp tường trình. Trong trường hợp này, "công việc" là sự thu hồi thực tế của thời gian hiện tại (được cấp, nó xảy ra trong ngữ cảnh có thể là một "công việc" đắt tiền hơn nhiều, việc ghi nhật ký một tin nhắn).NLog giảm thiểu chi phí nhận được thời gian hiện tại bằng cách chỉ nhận được thời gian "thực" (thông qua DateTime.Now) nếu số lượng đánh dấu hiện tại khác với số lượng đánh dấu trước đó. Điều này không thực sự áp dụng trực tiếp cho câu hỏi của bạn, nhưng đó là một cách thú vị để "tăng tốc" thời gian truy xuất hiện tại.

internal class CurrentTimeGetter  
{   
    private static int lastTicks = -1;   
    private static DateTime lastDateTime = DateTime.MinValue;   

    /// <summary>   
    /// Gets the current time in an optimized fashion.   
    /// </summary>   
    /// <value>Current time.</value>   

    public static DateTime Now   
    {    
    get    
    {     
     int tickCount = Environment.TickCount;     
     if (tickCount == lastTicks)     
     {      
     return lastDateTime;     
     }     
     DateTime dt = DateTime.Now;     
     lastTicks = tickCount;     
     lastDateTime = dt;     
     return dt;    
    }   
    }  
} 

// It would be used like this: 
DateTime timeToLog = CurrentTimeGetter.Now; 

Trong bối cảnh của câu hỏi của bạn, bạn có thể có thể "cải thiện" hiệu quả hoạt động của mã vòng lặp thời gian của bạn như thế này:

private static void MethodA_PrecalcEndTime() 
{ 
    int cnt = 0; 
    var doneTime = DateTime.Now.AddSeconds(1); 
    var startDT = CurrentTimeGetter.Now; 
    while (CurrentTimeGetter.Now <= doneTime)        
    {   
    cnt++; 
    } 
    var endDT = DateTime.Now; 
    Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);      }        
} 

Nếu CurrentTimeGetter.Now được gọi là như vậy thường xuyên rằng thời gian quay trở lại sẽ là nhiều lần liên tiếp, chỉ phải trả chi phí Environment.TickCount. Tôi không thể nói nếu nó thực sự giúp với hiệu suất khai thác NLog như vậy mà bạn sẽ nhận thấy hay không.

Tôi không biết rằng nó thực sự giúp ích cho câu hỏi của bạn, hoặc nếu bạn cần trợ giúp nữa, nhưng tôi nghĩ rằng nó sẽ là một ví dụ thú vị để tận dụng một hoạt động nhanh hơn (Environment.Ticks) hoạt động tương đối chậm (DateTime.Now) trong một số trường hợp.

+0

theo thử nghiệm của tôi, chức năng được cung cấp chậm hơn 3,5 lần như truy vấn DateTime. UtcNow –

2

Theo như tôi có thể biết, DateTime.UtcNow (không bị nhầm lẫn với DateTime.Now, chậm hơn nhiều) là cách nhanh nhất mà bạn có thể nhận được thời gian. Trong thực tế, bộ nhớ đệm nó theo cách @ wageoghe đề xuất giảm hiệu suất signifficantly (trong các bài kiểm tra của tôi, đó là 3,5 lần).

Trong ILSpy, UtcNow trông như thế này:

[__DynamicallyInvokable] 
public static DateTime UtcNow 
{ 
    [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical] 
    get 
    { 
     long systemTimeAsFileTime = DateTime.GetSystemTimeAsFileTime(); 
     return new DateTime((ulong)(systemTimeAsFileTime + 504911232000000000L | 4611686018427387904L)); 
    } 
} 

Tôi nghĩ rằng, điều này cho thấy hàm được inlined bởi trình biên dịch để đạt được tốc độ tối đa. Có thể có các cách nhanh hơn để có thời gian, nhưng cho đến nay, tôi chưa thấy một số

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