2011-11-16 34 views
6

Tôi đã xem qua một sự khác biệt về tốc độ bằng cách sử dụng hai cấu trúc sau:hiệu suất constructor tĩnh và lý do tại sao chúng tôi không thể xác định beforefieldinit

public struct NoStaticCtor 
{ 
    private static int _myValue = 3; 
    public static int GetMyValue() { return _myValue; } 
} 

public struct StaticCtor 
{ 
    private static int _myValue; 
    public static int GetMyValue() { return _myValue; } 
    static StaticCtor() 
    { 
     _myValue = 3; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     long numTimes = 5000000000; // yup, 5 billion 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      NoStaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("No static ctor: {0}", sw.Elapsed); 

     sw.Restart(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      StaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("with static ctor: {0}", sw.Elapsed); 
    } 
} 

nào tạo ra kết quả:

Release (x86), no debugger attached: 
No static ctor: 00:00:05.1111786 
with static ctor: 00:00:09.9502592 

Release (x64), no debugger attached: 
No static ctor: 00:00:03.2595979 
with static ctor: 00:00:14.5922220 

Trình biên dịch tạo ra một constructor tĩnh cho NoStaticCtor giống hệt với khai báo một cách rõ ràng trong StaticCtor. Tôi hiểu rằng trình biên dịch sẽ chỉ phát ra beforefieldinit khi một hàm tạo tĩnh không được định nghĩa rõ ràng.

Họ sản xuất gần đang il giống hệt nhau, ngoại trừ một sự khác biệt, tuyên bố struct với beforefieldinit, đó là nơi tôi cảm thấy sự khác biệt nằm kể từ khi tôi biết nó quyết định khi các nhà xây dựng loại được gọi là, mặc dù tôi có thể không hoàn toàn tìm ra lý do tại sao có sự khác biệt như vậy. Giả sử nó không phải là gọi kiểu hàm tạo mỗi lần lặp lại, vì một hàm tạo kiểu chỉ có thể được gọi một lần.

Vì vậy,

1) Tại sao sự khác biệt về thời gian giữa struct với beforefieldinit và một mà không có? (Tôi tưởng tượng JITer đang làm một cái gì đó thêm trong vòng lặp, tuy nhiên, tôi không có đầu mối làm thế nào để xem đầu ra của JITer để xem những gì.

2) Tại sao các nhà thiết kế trình biên dịch) không làm cho tất cả các cấu trúc beforefieldinit mặc định và b) không cung cấp cho nhà phát triển khả năng xác định rõ hành vi đó? Tất nhiên, điều này giả sử bạn không thể, vì tôi đã không thể tìm thấy một cách.


Edit:

. I modified the code, về cơ bản chạy mỗi vòng lặp lần thứ hai, mong đợi một sự cải thiện, nhưng nó đã không nhiều:

No static ctor: 00:00:03.3342359 
with static ctor: 00:00:14.6139917 
No static ctor: 00:00:03.2229995 
with static ctor: 00:00:12.9524860 
Press any key to continue . . . 

Tôi đã làm điều này vì tôi, mặc dù tốt, lẽ, tuy nhiên không chắc nó là, JITer đã thực sự gọi hàm tạo kiểu mỗi lần lặp lại. Dường như với tôi JITer sẽ biết constructor kiểu đã được gọi và không phát ra mã để làm điều đó khi vòng lặp thứ hai được biên dịch.

Ngoài câu trả lời Motti của: This code tạo ra kết quả tốt hơn, vì sự khác biệt trong JITing, các JITing của DoSecondLoop không phát ra kiểm tra ctor tĩnh, bởi vì nó phát hiện nó đã được thực hiện trước đó trong DoFirstLoop, khiến mỗi vòng lặp để thực hiện ở cùng một tốc độ. (~ 3 giây)

+2

Một số quan điểm về số lượng lớn là bắt buộc tại đây. Chi phí bạn đo được là * một nano giây *. Có, đó là về những gì một hướng dẫn thử nghiệm nhảy + mất. –

+0

@Tôi biết chi phí rất nhỏ. Tôi sẽ không bao giờ thực sự viết mã như thế này được sử dụng trong sản xuất. Tôi hiện đang trên một "làm thế nào để CLR làm việc" stint và tôi đã rối tung xung quanh với những điều bất thường. Tôi thực sự chỉ cố gắng hiểu tại sao JITer đưa ra quyết định, dựa trên các thuộc tính mà trình biên dịch phát ra. Có lẽ tôi nên mua một cuốn sách. –

Trả lời

10

Lần đầu tiên loại của bạn được truy cập, ctor tĩnh (được tạo ra một cách rõ ràng hoặc ngầm) phải được thực hiện. Khi trình biên dịch JIT biên dịch IL thành các lệnh gốc, nó kiểm tra xem ctor tĩnh cho kiểu đó đã được thực thi chưa và nếu không phát ra mã gốc kiểm tra (một lần nữa) liệu ctor tĩnh đã được thực thi chưa và thực thi nó.

Mã jitted được lưu trong bộ nhớ cache cho các cuộc gọi trong tương lai của cùng một phương thức (và đây là lý do tại sao mã phải kiểm tra lại nếu ctor tĩnh được thực hiện).

Vấn đề là, nếu mã jitted kiểm tra ctor tĩnh là trong một vòng lặp, kiểm tra này sẽ xảy ra trong mỗi lần lặp.

Khi có sẵn beforefieldinit, trình biên dịch JIT được phép tối ưu hóa mã, bằng cách kiểm tra lời gọi ctor tĩnh TRƯỚC KHI nhập vòng lặp. Nếu không có, tối ưu hóa này không được phép.

Trình biên dịch C# tự động quyết định cho chúng tôi khi phát ra thuộc tính này. Hiện tại (C# 4) phát ra nó chỉ khi mã KHÔNG định nghĩa rõ ràng một hàm tạo tĩnh. Ý tưởng đằng sau nó là nếu bạn tự định nghĩa một ctor tĩnh, thời gian có thể quan trọng hơn đối với bạn và ctor ststic không nên thực thi trước thời hạn. Điều này có thể có hoặc không đúng, nhưng bạn không thể thay đổi hành vi này.

Dưới đây là một liên kết đến một phần của hướng dẫn .NET trực tuyến của tôi mà giải thích điều này một cách chi tiết: http://motti.me/c1L

BTW, tôi không khuyên bạn sử dụng ctors tĩnh trên cấu trúc vì có tin hay không, họ đang không được bảo đảm để thực thi! Đây không phải là một phần của câu hỏi, vì vậy tôi sẽ không xây dựng, nhưng hãy xem chi tiết hơn nếu bạn quan tâm: http://motti.me/c1I (Tôi chạm chủ đề vào khoảng 2:30 vào video).

Tôi hy vọng điều này sẽ hữu ích!

+0

Không sử dụng ctors tĩnh trên structs là lời khuyên tuyệt vời. Tôi rất ý thức về cách CLR có thể không phải lúc nào cũng thực thi nó. Bây giờ, khi tính đến liên kết pastebin trong bản chỉnh sửa đầu tiên của tôi, JITer đang đặt kiểm tra ctor tĩnh trong * cả hai vòng lặp liên quan đến 'StaticCtor.GetMyValue()' thay vì chỉ trong vòng đầu tiên cho vòng lặp, mặc dù JITer biết nó được đặt kiểm tra trước đó trong mã. Về cơ bản, trong bất kỳ hàm nào mà một ctor tĩnh vẫn chưa được gọi, nó sẽ đặt kiểm tra đó trước khi * MỌI * truy cập không chỉ trước tiên, đúng không? –

+0

Hmm, bằng cách kiểm tra có vẻ như bạn đã nói, trình biên dịch JIT đưa ra quyết định có nên đặt thử nghiệm trên cơ sở từng phương thức đã trích xuất hay không. Tôi đã sửa đổi mã của bạn thành hai phương thức riêng biệt cho mỗi cuộc gọi (có & không có ctor) và tăng hiệu suất lần thứ hai: http://pastebin.com/WyQWz5ar. Tôi không chắc liệu đây là tài liệu hay chi tiết triển khai, có thể thay đổi ở bất kỳ bản cập nhật nào của trình biên dịch JIT. Ngoài ra, như tôi chắc chắn bạn đã biết, việc thực hiện trình biên dịch JIT có thể khác nhau giữa các nền tảng. –

+0

Vâng, tôi thấy nó thật thú vị. Như tôi đã nhận xét ở trên, tôi đang ở trong một giai đoạn mà tôi chỉ muốn gây rối xung quanh/hiểu CLR, và hành vi này xuất hiện. Tôi bị cuốn hút bởi cách JIT quyết định khi nào và phải làm gì. Cảm ơn! :) –

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