2013-04-16 34 views
7

Tôi đã xem xét điều này trong khi thực hiện một số điểm chuẩn.Đúc cấu trúc để đối tượng để so sánh null không gây ra boxing?

bool b; 
MyStruct s; 
for (int i = 0; i < 10000000; i++) 
{ 
    b = (object)s == null; 
} 

gỡ lỗi: 200 ms

phát hành: 5 ms

bool b; 
MyStruct? s = null; 
for (int i = 0; i < 10000000; i++) 
{ 
    b = (object)s == null; 
} 

gỡ lỗi: 800 ms

phát hành: 800 ms

Tôi có thể hiểu được kết quả này vì việc đúc cấu trúc không có giá trị thành object mang lại cho tôi một kiểu đóng hộp của cấu trúc đó. Nhưng tại sao không đúc struct s đến object để thực hiện so sánh null (như trong phương pháp đầu tiên) dẫn đến hiệu suất giống nhau? Có phải trình biên dịch đang tối ưu hóa cuộc gọi để trả lại false luôn như một cấu trúc không thể là rỗng?

+0

mã này sẽ không biên dịch; Lỗi 1 Sử dụng biến địa phương chưa được gán 'cho vòng lặp thứ hai – Fredou

+0

@Fredou Đúng vậy. Một lỗi đánh máy trong thực tế. Tôi sẽ cập nhật câu trả lời của tôi với một số điểm chuẩn - Tôi đã tìm thấy lỗi trong mã thời gian – nawfal

+0

mã này, khi được biên dịch, dường như đang chạy vòng lặp trống, bạn nên đăng mã điểm chuẩn thực tế của mình vì tôi không thấy cách bạn có thể nhận 7500ms – Fredou

Trả lời

8

Có, trình biên dịch đang tối ưu hóa nó.

Nó biết rằng một cấu trúc không bao giờ có thể rỗng, do đó kết quả đúc nó vào một đối tượng không bao giờ có thể rỗng - vì vậy nó sẽ chỉ đặt b thành sai trong mẫu đầu tiên. Thực tế, nếu bạn sử dụng Resharper, nó sẽ cảnh báo bạn rằng biểu thức luôn sai.

Đối với khóa học thứ hai, giá trị rỗng có thể là null vì vậy nó phải thực hiện kiểm tra.

(Bạn cũng có thể sử dụng Reflector để kiểm tra mã IL biên dịch tạo ra để xác minh điều này.)

Các mã kiểm tra ban đầu là không tốt bởi vì trình biên dịch biết rằng struct nullable sẽ luôn luôn được null và do đó cũng sẽ tối ưu hóa vòng lặp đó. Không chỉ vậy, nhưng trong bản phát hành, trình biên dịch nhận ra rằng b không được sử dụng và tối ưu hóa toàn bộ vòng lặp.

Để ngăn chặn điều đó, và để hiển thị những gì sẽ xảy ra trong mã thực tế hơn, kiểm tra nó như vậy:

using System; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      bool b = true; 
      MyStruct? s1 = getNullableStruct(); 
      Stopwatch sw = Stopwatch.StartNew(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       b &= (object)s1 == null; // Note: Redundant cast to object. 
      } 

      Console.WriteLine(sw.Elapsed); 

      MyStruct s2 = getStruct(); 
      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       b &= (object)s2 == null; 
      } 

      Console.WriteLine(sw.Elapsed); 
     } 

     private static MyStruct? getNullableStruct() 
     { 
      return null; 
     } 

     private static MyStruct getStruct() 
     { 
      return new MyStruct(); 
     } 
    } 

    public struct MyStruct {} 
} 
+1

trong mã này, cả hai vòng lặp sẽ có cơ thể trống – Fredou

+1

@Fredou Tôi đã thêm một số mã kiểm tra rõ ràng để ngăn chặn vấn đề đó, vì vậy điều này chứng tỏ nó đúng cách. –

+1

trong thực tế để nhân rộng các hành vi tất cả những gì bạn cần là '& =' trong mã ban đầu, không cần phải tạo ra phương thức trả về cấu trúc – Fredou

3

trên thực tế cả hai vòng lặp sẽ có một cơ thể trống rỗng khi biên soạn!

để làm cho vòng lặp cư xử thứ hai, bạn sẽ phải loại bỏ các (object) đúc

đây là những gì nó trông giống như khi tôi biên dịch mã của bạn,

public struct MyStruct 
{ 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     test1(); 
     test2(); 
    } 

    public static void test1() 
    { 
     Stopwatch sw = new Stopwatch(); 
     bool b; 
     MyStruct s; 
     for (int i = 0; i < 100000000; i++) 
     { 
      b = (object)s == null; 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 

    public static void test2() 
    { 
     Stopwatch sw = new Stopwatch(); 
     bool b; 
     MyStruct? s = null; 
     for (int i = 0; i < 100000000; i++) 
     { 
      b = (object)s == null; 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 
} 

IL:

các MyStruct (trống vì bạn không cung cấp bất kỳ)

.class public sequential ansi sealed beforefieldinit ConsoleApplication1.MyStruct 
extends [mscorlib]System.ValueType 
{ 
    .pack 0 
    .size 1 

} // end of class ConsoleApplication1.MyStruct 

firs t vòng lặp trong ví dụ của bạn

.method public hidebysig static 
void test1() cil managed 
{ 
// Method begins at RVA 0x2054 
// Code size 17 (0x11) 
.maxstack 2 
.locals init (
    [0] valuetype ConsoleApplication1.MyStruct s, 
    [1] int32 i 
) 

IL_0000: ldc.i4.0 
IL_0001: stloc.1 
IL_0002: br.s IL_0008 
// loop start (head: IL_0008) 
    IL_0004: ldloc.1 
    IL_0005: ldc.i4.1 
    IL_0006: add 
    IL_0007: stloc.1 

    IL_0008: ldloc.1 
    IL_0009: ldc.i4 100000000 
    IL_000e: blt.s IL_0004 
// end loop 

IL_0010: ret 
} // end of method Program::test1 

vòng lặp thứ hai

.method public hidebysig static 
void test2() cil managed 
{ 
// Method begins at RVA 0x2074 
// Code size 25 (0x19) 
.maxstack 2 
.locals init (
    [0] valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct> s, 
    [1] int32 i 
) 

IL_0000: ldloca.s s 
IL_0002: initobj valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct> 
IL_0008: ldc.i4.0 
IL_0009: stloc.1 
IL_000a: br.s IL_0010 
// loop start (head: IL_0010) 
    IL_000c: ldloc.1 
    IL_000d: ldc.i4.1 
    IL_000e: add 
    IL_000f: stloc.1 

    IL_0010: ldloc.1 
    IL_0011: ldc.i4 100000000 
    IL_0016: blt.s IL_000c 
// end loop 

IL_0018: ret 
} // end of method Program::test2 
Các vấn đề liên quan