2017-08-29 13 views
10

Mã số:Lý do của các khoảng thời gian khác nhau của các khối mã thực thi như thế nào?

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     const int iterCount = 999999999; 

     var sum1 = 0; 
     var sum2 = 0; 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum1 += i; 
      Console.WriteLine(sum1); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum2 += i; 
      Console.WriteLine(sum2); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     Console.ReadLine(); 
    } 

    private class Dis : IDisposable 
    { 
     public void Dispose(){} 
    } 
} 

Hai khối giống hệt nhau trong cách sử dụng giống hệt nhau.

Output:

2051657985 
00:00:00.3690996 
2051657985 
00:00:02.2640266 

khối thứ hai mất 2.2 giây! Nhưng nếu để loại bỏ việc sử dụng, thời lượng trở nên giống nhau (~ 0.3s, như lần đầu tiên). Tôi đã thử với .net framework 4.5 và .net core 1.1, trong bản phát hành, kết quả giống nhau.

Ai đó có thể giải thích hành vi đó không?

+2

Có thể thử với biến thay vì 'Console.WriteLine'?Đó có thể là vấn đề. – ispiro

+0

Khi tôi chạy mã của bạn trong linqpad nó mang lại cho tôi ~ 2,5 giây cho cả hai khối mã. Khi chạy trên ideone nó cung cấp cho ~ 0,3 giây cho cả hai khối: https://ideone.com/ReOpaH. – Chris

+2

Đồng thời sử dụng đồng hồ bấm giờ thích hợp. Bạn gọi sw biến datetime của bạn có nghĩa là bạn đang muốn sử dụng một đồng hồ bấm giờ nhưng sau đó bạn không sử dụng một. – Chris

Trả lời

13

Bạn phải xem mã máy mà jitter tạo để xem lý do cơ bản. Sử dụng Công cụ> Tùy chọn> Gỡ lỗi> Chung> bỏ tùy chọn tối ưu hóa Suppress JIT. Chuyển sang bản dựng. Đặt điểm ngắt trên vòng đầu tiên và thứ hai. Khi nó truy cập sử dụng Debug> Windows> Tháo gỡ.

Bạn sẽ thấy mã máy tính cho các cơ quan của vòng lặp for:

    sum1 += i; 
00000035 add   esi,eax 

Và:

    sum2 += i; 
000000d9 add   dword ptr [ebp-24h],eax 

Hoặc nói cách khác, biến sum1 được lưu trữ trong CPU đăng ký esi. Nhưng biến số sum2 được lưu trữ trong bộ nhớ, trên khung ngăn xếp của phương thức. Lớn, khác biệt lớn. Đăng ký rất nhanh, bộ nhớ chậm. Bộ nhớ cho khung ngăn xếp sẽ nằm trong bộ nhớ cache L1, trên các máy hiện đại truy cập bộ đệm đó có độ trễ là 3 chu kỳ. Bộ đệm cửa hàng sẽ nhanh chóng bị choáng ngợp với số lượng ghi lớn và làm cho bộ xử lý ngừng hoạt động.

Tìm cách giữ biến trong sổ đăng ký CPU là one of the primary jitter optimization duties. Nhưng điều đó có những hạn chế, đặc biệt là x86 có vài thanh ghi có sẵn. Khi tất cả chúng được sử dụng hết thì jitter không có lựa chọn nào khác ngoài việc sử dụng bộ nhớ thay thế. Lưu ý rằng câu lệnh using có thêm biến cục bộ ẩn dưới mui xe, đó là lý do tại sao nó có hiệu lực.

Lý tưởng nhất là trình tối ưu hóa jitter sẽ có lựa chọn tốt hơn về cách phân bổ thanh ghi. Sử dụng chúng cho các biến vòng lặp (mà nó đã làm) và các biến tổng hợp. Trình biên dịch trước thời hạn sẽ nhận được quyền đó, có đủ thời gian để thực hiện phân tích mã. Nhưng một trình biên dịch ngay trong thời gian hoạt động dưới các ràng buộc thời gian nghiêm ngặt.

cơ bản phản biện pháp này là:

  • Chia tay mã vào phương pháp riêng biệt để một thanh ghi như ESI có thể được tái sử dụng.
  • Loại bỏ quá trình jitter buộc (Dự án> Thuộc tính> tab Xây dựng> chưa được chọn "Ưu tiên 32 bit"). x64 cung cấp 8 thanh ghi bổ sung.

Viên đạn cuối cùng có hiệu lực đối với bộ phận x64 cũ (mục tiêu .NET 3.5 để sử dụng), nhưng không cho ghi xít jitter (còn gọi là RYuJIT) được cung cấp lần đầu tiên trong 4.6. Viết lại là cần thiết vì jitter kế thừa mất quá nhiều thời gian để tối ưu hóa mã. Thất vọng, RyuJIT có một sở trường cho thất vọng, tôi nghĩ rằng trình tối ưu hóa của nó có thể làm một công việc tốt hơn ở đây.

+0

Cảm ơn bạn, trông giống như câu trả lời! thú vị, nếu chúng ta di chuyển khai báo sum2 (var sum2 = 0;) để đặt giữa các khối, thì các thanh ghi được sử dụng trong cả hai vòng lặp. –

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