2012-07-13 20 views
7

Tôi nhận thấy rằng thời gian khởi động sẽ khác nhau tùy thuộc vào tại đây tôi đã đặt một đoạn mã khởi tạo. Tôi nghĩ điều này thật kỳ lạ, vì vậy tôi đã viết một điểm chuẩn nhỏ, điều này đã khẳng định những nghi ngờ của tôi. Có vẻ như mã được thực thi trước khi phương thức main được gọi chậm hơn bình thường.Mã trong hàm dựng tĩnh chạy chậm hơn

Tại sao Benchmark(); chạy ở các tốc độ khác nhau tùy thuộc vào việc được gọi trước và sau đường dẫn mã thông thường?

Đây là mã chuẩn:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

Kết quả cho thấy Benchmark() chạy gần gấp đôi chậm cho cả các nhà xây dựng tĩnh và constructor cho static Program program tài sản:

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

Tăng gấp đôi số lần lặp trong vòng lặp chuẩn gây ra tất cả các lần tăng gấp đôi, cho thấy rằng hình phạt hiệu suất phát sinh không phải là hằng số, mà là một yếu tố.

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

Tại sao điều này xảy ra? Nó sẽ có ý nghĩa nếu khởi tạo sẽ chỉ là nhanh nếu nó được thực hiện nơi nó thuộc về. Thử nghiệm đã được thực hiện trong .NET 4, chế độ phát hành, tối ưu hóa trên.

+2

Câu hỏi chính xác là gì? – jcolebrand

+0

Cài đặt biên dịch? Phiên bản khung? – user7116

+0

Xin vui lòng xem nếu chỉnh sửa của tôi làm cho sence (Tôi không có ý tưởng tại sao), đã thử rằng trên .Net 4/phát hành với kết quả tương tự. –

Trả lời

3

Đây là một vấn đề rất thú vị. Tôi đã dành thời gian thử nghiệm với các biến thể của chương trình của bạn. Dưới đây là một vài quan sát:

  1. Nếu bạn di chuyển phương thức tĩnh Benchmark() vào một lớp khác, hình phạt hiệu suất cho hàm tạo tĩnh biến mất.

  2. Nếu bạn đặt phương thức Benchmark() thành phương pháp thể hiện, hình phạt hiệu suất sẽ biến mất.

  3. Khi tôi lập hồ sơ các trường hợp nhanh (1, 2) và các trường hợp chậm (3, 4), các trường hợp chậm sẽ tốn thêm thời gian trong các phương thức trợ giúp CLR, cụ thể là JIT_GetSharedNonGCStaticBase_Helper.

Dựa trên thông tin này, tôi có thể suy đoán về những gì đang diễn ra. CLR cần đảm bảo rằng mọi hàm tạo tĩnh thực hiện tối đa một lần. Một biến chứng là các nhà xây dựng tĩnh có thể tạo thành một chu kỳ (ví dụ, nếu lớp A chứa một trường tĩnh kiểu B và lớp B chứa một trường tĩnh kiểu A).

Khi thực thi bên trong một hàm tạo tĩnh, trình biên dịch JIT chèn kiểm tra xung quanh một số lời gọi phương thức tĩnh để ngăn chặn các chu kỳ vô hạn tiềm năng do phụ thuộc lớp tuần hoàn. Khi phương thức tĩnh được gọi từ bên ngoài của một hàm tạo tĩnh, CLR biên dịch lại phương thức để loại bỏ các kiểm tra.

Điều này sẽ khá gần với những gì đang diễn ra.

+0

Cảm ơn bạn đã tìm kiếm sự cố nhiều hơn, đặc biệt là việc lập hồ sơ. Tôi không chắc chắn về phân tích của bạn mặc dù; nó sẽ ngụ ý một chi phí liên tục trong các thử nghiệm của tôi. Tôi đã cập nhật câu hỏi của mình để cho thấy rằng hình phạt hiệu suất dường như là do yếu tố. – Zong

+0

Có một khoản phụ phí ** cho mỗi cuộc gọi IsPrime() ** vì mỗi cuộc gọi IsPrime phải được bao quanh bằng séc. Ví dụ: nếu bạn làm cho IsPrime đắt hơn (ví dụ: gọi IsPrime (1000000000 + 2 * i)), sự khác biệt giữa bốn trường hợp biến mất. –

+0

Tức là, nếu bạn tăng gấp đôi số lần lặp lại, bạn cũng tăng gấp đôi số lượng cuộc gọi IsPrime và do đó tăng gấp đôi số lần kiểm tra được thực hiện. –

3

Đây là một sự kiện được ghi nhận rất tốt.

Trình xây dựng tĩnh chậm. Thời gian chạy .net không đủ thông minh để tối ưu hóa chúng.

tham khảo: Performance penalty of static constructors

constructor tĩnh rõ ràng là tốn kém vì họ yêu cầu mà bộ thực thi để bảo đảm rằng giá trị được đặt chính xác trước khi bất kỳ thành viên của lớp được truy cập. Chi phí chính xác là phụ thuộc vào kịch bản, nhưng nó có thể khá đáng chú ý trong một số trường hợp.

+0

Tôi không nghĩ rằng bài viết được liên kết sẽ áp dụng. Bài báo nói rằng một kiểu với một hàm tạo tĩnh được đánh dấu beforeFieldInit, và do đó làm việc với kiểu đó phát sinh thêm một chi phí thời gian chạy. Tuy nhiên, OP không so sánh hai loại, một với một hàm tạo tĩnh và một không có. Chỉ có một loại trong toàn bộ điểm chuẩn và loại đó có một hàm tạo tĩnh. Vì vậy, một cái gì đó khác đang xảy ra. –

+0

Bạn nói đúng; điểm chuẩn của tôi thực sự cho thấy hiệu suất của hai trường hợp trong bài viết là tương đương hoặc ít hơn. – Zong

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