2011-02-06 60 views
17
if (x == null) x = new X(); 

soInstantiating một biến nếu null

x = x ?? new X(); 

nào trong hai số đó là thực sự performant hơn? một khi biên dịch họ có hiệu quả gió lên như điều tương tự (x = x; sẽ là một NO-OP như là một kết quả)?

+0

Tôi muốn nói câu hỏi thứ hai sẽ nhanh hơn vì ?? toán tử có một mục đích cụ thể, nhưng bạn có thể biên dịch hai đoạn mã đó và so sánh IL được tạo ra và/hoặc chạy thử nghiệm để xem thử nghiệm nào nhanh hơn. – tenor

+0

@ MasterMax1313 sự khác biệt IL không phải là nhiều không liên quan mà bạn downvote .. vẫn còn tôi đã cập nhật câu trả lời của tôi –

+0

Lưu ý rằng không phải là thread-an toàn. Và một trong những thường hy vọng getters được thread-an toàn. Tôi khuyên bạn nên sử dụng 'Lazy 'nếu bạn * thực sự * cần khởi tạo lười biếng. Đó là một chút chậm hơn nhưng tránh một số khó khăn. – CodesInChaos

Trả lời

26

Nhìn vào mã ngôn ngữ trung gian có sự khác biệt:

.method private hidebysig instance void Method1() cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: ldfld class X Program::x 
    L_0006: brtrue.s L_0013 
    L_0008: ldarg.0 
    L_0009: newobj instance void X::.ctor() 
    L_000e: stfld class X Program::x 
    L_0013: ret 
} 

.method private hidebysig instance void Method2() cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: ldarg.0 
    L_0002: ldfld class X Program::x 
    L_0007: dup 
    L_0008: brtrue.s L_0010 
    L_000a: pop 
    L_000b: newobj instance void X::.ctor() 
    L_0010: stfld class X Program::x 
    L_0015: ret 
} 

Dưới đây là đoạn code tôi biên soạn để có được điều này:

void Method1() 
{ 
    if (x == null) x = new X(); 
} 

void Method2() 
{ 
    x = x ?? new X(); 
} 

Để chắc chắn đó là nhanh hơn bạn nên thời gian cả.

 
Method  Initial condition Iterations per second 
--------------------------------------------------- 
NullCheck x is null   33 million 
Coalesce x is null   33 million 
NullCheck x is not null  40 million 
Coalesce x is not null  33 million 

Kết luận:

  • Họ là như nhau trong trường hợp này, nơi các giá trị ban đầu null.
  • Phương pháp sử dụng câu lệnh if nhanh hơn đáng kể so với toán tử kết hợp rỗng khi x không phải là null.

Sự khác biệt khi x là không vẻ rỗng như nó có thể là do các nhà điều hành coalescing rỗng gán giá trị của x trở lại x (stfld trong IL), trong khi việc kiểm tra null nhảy qua các hướng dẫn stfld khi x là có giá trị.

Cả hai quá nhanh đến nỗi bạn phải có một vòng lặp chặt chẽ rất để nhận thấy sự khác biệt. Bạn chỉ nên thực hiện các loại tối ưu hóa hiệu suất này nếu bạn đã lược tả mã của mình với dữ liệu của mình. Các tình huống khác nhau, các phiên bản khác nhau của .NET, các trình biên dịch khác nhau, v.v. có thể tạo ra các kết quả khác nhau.

Trong trường hợp ai đó muốn biết làm thế nào tôi có những kết quả hoặc sao chép chúng, đây là đoạn code tôi sử dụng:

using System; 

class X { } 

class Program 
{ 
    private X x; 

    private X xNull = null; 
    private X xNotNull = new X(); 

    private void Method1Null() 
    { 
     x = xNull; 
     if (x == null) x = xNotNull; 
    } 

    private void Method2Null() 
    { 
     x = xNull; 
     x = x ?? xNotNull; 
    } 

    private void Method1NotNull() 
    { 
     x = xNotNull; 
     if (x == null) x = xNotNull; 
    } 

    private void Method2NotNull() 
    { 
     x = xNotNull; 
     x = x ?? xNotNull; 
    } 

    private const int repetitions = 1000000000; 

    private void Time(Action action) 
    { 
     DateTime start = DateTime.UtcNow; 
     for (int i = 0; i < repetitions; ++i) 
     { 
      action(); 
     } 
     DateTime end = DateTime.UtcNow; 
     Console.WriteLine(repetitions/(end - start).TotalSeconds); 
    } 

    private void Run() 
    { 
     Time(() => { Method1Null(); }); 
     Time(() => { Method2Null(); }); 
     Time(() => { Method1NotNull(); }); 
     Time(() => { Method2NotNull(); }); 
     Console.WriteLine("Finished"); 
     Console.ReadLine(); 
    } 

    private static void Main() 
    { 
     new Program().Run(); 
    } 
} 

Disclaimer: Không có điểm chuẩn là hoàn hảo, và bechmark đây là xa từ hoàn hảo, chủ yếu để giữ mọi thứ đơn giản. Tôi đã chạy nhiều thử nghiệm khác nhau, ví dụ: với các phương pháp theo thứ tự khác nhau, có và không có "khởi động" đầu tiên, qua các khoảng thời gian khác nhau, v.v ... tôi nhận được kết quả gần như giống nhau mỗi lần. Tôi không có bất cứ điều gì để chứng minh một cách này hay cách khác vì vậy bất kỳ sự thiên vị nào ưu tiên một phương pháp hay phương pháp khác là ngẫu nhiên.

+1

+1 Cảm ơn bạn đã cung cấp IL. –

+0

+1 cho câu trả lời nhanh ... bởi thời gian tôi đã thực hiện mã –

+5

khác nhau IL không nhất thiết có nghĩa là JIT dịch chúng thành hai phần khác nhau của mã máy. –

16

Tôi sẽ không lo lắng về việc tối ưu hóa sớm này. Tôi chắc rằng các nhà thiết kế của trình biên dịch C# đủ thông minh để làm điều này cho bạn.

6

Không có interresting hiệu suất khác biệt tại tất cả. Điều quan trọng nhất là if (x == null) x = new X();dễ đọc hơn nhiều.

Để trả lời câu hỏi ban đầu của bạn: trình biên dịch thông minh, tối ưu hóa sẽ biên dịch x = x ?? new X() theo cùng cách như if (x == null) x = new X();.

Do đó, hãy để tối ưu hóa vi mô trên trình biên dịch và tập trung vào khả năng đọc và làm rõ mã.


Cập nhật: Để tìm hiểu thêm về các hoạt động tối ưu hóa đọc this article on wikipedia và sau đó, trong sự tôn trọng bản chất của câu hỏi của bạn, Google cho “premature optimization is the root of all evil”.

+10

Đó là một chút chủ quan - Nên là " dễ đọc hơn cho tôi " – PostMan

+2

Tôi hoàn toàn đồng ý; bạn nên thiết kế cho bảo trì cho rằng sự khác biệt hiệu suất là không. –

+1

@PostMan Từ quan điểm của tôi, và tôi cá rằng hầu hết mọi người đều cảm thấy giống nhau, bất kỳ cấu trúc nào tương tự như 'x = x' mùi với các tác dụng phụ và khiến người đọc thực sự phải suy nghĩ về nó nhiều hơn trái ngược với đồng bằng 'if'. –

4

Như những người khác đã đề cập, sự khác biệt hiệu suất giữa hai không phải là khác nhau, tuy nhiên có một sự khác biệt quan trọng giữa hai, mặc dù nó không phải như vậy rõ ràng từ ví dụ của bạn.

if (x == null) x = new X(); tạo điều kiện chạy tiềm năng trong đó x có thể được tạo mới bằng một chuỗi khác giữa kiểm tra giá trị rỗng và tạo, do đó mất đối tượng.

x = x ?? new X(); tạo bản sao của biến đầu tiên, do đó không có điều kiện chủng tộc tiềm năng nào.

Đây là vấn đề trong trường hợp bạn đang tham chiếu đối tượng. Ví dụ:

if (x != null) x.DoSomething(); // this can cause a null reference exception 
           // if another thread nulls x between the check 
           // and the call to DoSomething() 

(x = x ?? new X()).DoSomething()  // this should be fine. 
+1

Điều này không * ngăn chặn * điều kiện chủng tộc nói chung (ví dụ như không được coi là bất kỳ hình thức ngầm nào "bảo vệ luồng" mà không có ngữ cảnh được phân tích và tinh tế) - nó chỉ đảm bảo rằng trường hợp sau luôn được gọi khi đối tượng không null (bất kể nó có thể là gì). Tôi sẽ bỏ phiếu, nhưng tôi ra ngoài hôm nay: ( –

+0

-1 lý do của bạn là sai - 1) đề cập đến một biến chỉ một lần trong một biểu thức không có nghĩa là vị trí bộ nhớ sẽ được truy cập chỉ một lần trong quá trình đánh giá biểu thức đó, 2) thread-an toàn/biến đọc/ghi atomicity (cuộc đua điều kiện phòng chống) không có gì để làm với trường hợp này. –

+0

@Ondrej Tucny - Tôi không nói gì về việc đề cập đến biến một lần. Tôi đã đề cập đến IL được đăng bởi những người khác, nơi phiên bản coalescing làm cho một bản sao của biến trước khi nó được sử dụng, trong khi phiên bản nếu không. và tôi cũng không bao giờ nói bất cứ điều gì về an toàn thread hoặc atomicity .. Tôi đã đề cập đến thực tế là nó có thể tog et một ngoại lệ tham chiếu null với phiên bản nếu, trong khi ?? phiên bản bạn sẽ không. Có hay không nhà nước là chính xác là một câu chuyện khác nhau. Tôi sẽ bầu bạn xuống để đưa ra các giả định, đặc biệt là những giả định không hợp lệ ... nếu tôi có thể. –

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