2009-05-26 36 views
33

Tại sao các loại niêm phong nhanh hơn?Tại sao các loại niêm phong nhanh hơn?

Tôi tự hỏi về chi tiết sâu hơn về lý do tại sao điều này đúng.

+2

Vui lòng xem: http://stackoverflow.com/questions/268251/why-seal-a- lớp – Shog9

+0

Có phải không? Tôi không biết ... CLR có thể tối ưu hóa bảng công cụ phương thức, biết rằng nó không thể phát triển được nữa. – harpo

+1

@harpo: xem tham chiếu này: http://msdn.microsoft.com/en-us/library/ms173150.aspx. Tôi đã thêm nó vào câu trả lời của tôi, nhưng thực tế đơn giản là họ không nói nhiều về TẠI SAO, vì vậy tôi quyết định không thêm nó ... –

Trả lời

36

Ở mức thấp nhất, trình biên dịch có thể thực hiện tối ưu hóa vi mô khi bạn có các lớp được niêm phong.

Nếu bạn đang gọi phương thức trên lớp được niêm phong và loại được khai báo vào thời gian biên dịch là lớp được niêm phong, trình biên dịch có thể thực hiện cuộc gọi phương thức (trong hầu hết các trường hợp) bằng lệnh gọi IL thay vì callvirt IL hướng dẫn. Điều này là do mục tiêu phương pháp không thể bị ghi đè. Gọi loại bỏ một kiểm tra null và làm một tra cứu vtable nhanh hơn so với callvirt, vì nó không phải kiểm tra các bảng ảo.

Đây có thể là một cải tiến rất, rất nhỏ đối với hiệu suất.

Điều đó đang được nói, tôi hoàn toàn sẽ bỏ qua điều đó khi quyết định có niêm phong một lớp hay không. Đánh dấu một loại niêm phong thực sự phải là một quyết định thiết kế, không phải là một quyết định hiệu suất. Bạn có muốn mọi người (bao gồm cả chính bạn) đến lớp con tiềm năng từ lớp học của bạn, bây giờ hoặc trong tương lai không? Nếu có, đừng đóng dấu. Nếu không, hãy đóng dấu. Đó thực sự phải là yếu tố quyết định.

+4

Khi thiết kế, có thể là một ý tưởng tốt để níu chặt các loại công khai mà không cần phải mở rộng vì việc loại bỏ một lớp trong phiên bản tương lai là một thay đổi không phá vỡ trong khi ngược lại không đúng. –

+1

@Neil Williams: Tôi đồng ý. Nói chung, kể từ khi unsealing một lớp là an toàn, và niêm phong không, nếu bạn đang làm cho các thư viện công cộng, niêm phong có thể là một điều tốt đẹp để làm. Một lần nữa, mặc dù, điều này làm cho niêm phong một sự lựa chọn thiết kế nhiều hơn một vấn đề hiệu suất. –

+0

Tôi nghĩ rằng đó là do nội tuyến. Trình biên dịch C# luôn sử dụng callvirt vì nó thích tác dụng phụ không có dấu kiểm của mã IL đó. –

9

Về cơ bản, nó phải làm với thực tế là họ không cần phải lo lắng về các phần mở rộng cho một bảng chức năng ảo; các loại niêm phong không thể được mở rộng, và do đó, thời gian chạy không cần phải quan tâm về cách chúng có thể đa hình.

5

Nếu trình biên dịch JIT thấy một cuộc gọi đến một phương pháp ảo bằng cách sử dụng một loại niêm phong, nó có thể tạo ra mã hiệu quả hơn bằng cách gọi phương thức không thực sự. Bây giờ gọi phương thức không phải ảo nhanh hơn vì không cần thực hiện tra cứu vtable. IMHO đây là tối ưu hóa vi mô nên được sử dụng như một phương sách cuối cùng để cải thiện hiệu suất của một ứng dụng. Nếu phương thức của bạn có chứa bất kỳ mã nào, phiên bản ảo sẽ chậm hơn một cách không đáng kể so với chi phí thực thi mã.

+2

Tại sao nên đây có phải là phương sách cuối cùng không? Tại sao không chỉ đóng dấu các lớp học của bạn theo mặc định? Nó thường chỉ được coi là một microopimization nếu có một số chi phí liên quan đến nó (ít mã có thể đọc được, hoặc thời gian phát triển hơn thường). Nếu không có nhược điểm để làm điều đó, tại sao không làm điều đó hay không hiệu suất là một vấn đề? – jalf

+1

Khi bạn đóng dấu một lớp, bạn ngăn chặn việc sử dụng thừa kế. Điều này có thể làm cho việc phát triển trở nên khó khăn hơn, nó có thể ngăn chặn làm việc xung quanh một số lỗi nhất định. Lý tưởng nhất, người ta sẽ nghĩ về nó và thiết kế cho thừa kế, và làm cho đồng bằng những gì được thiết kế để được mở rộng và niêm phong mọi thứ khác. Che dấu một cách mù quáng mọi thứ quá hạn chế. – Eddie

3

Để mở rộng câu trả lời của người khác, một lớp được niêm phong (tương đương với một lớp cuối cùng trong Java) không thể được mở rộng. Điều này có nghĩa là bất cứ khi nào trình biên dịch nhìn thấy một phương thức của lớp này được sử dụng, trình biên dịch biết hoàn toàn rằng không cần gửi đi thời gian chạy. Nó không phải kiểm tra lớp để xem động mà phương thức nào trong đó lớp trong hệ thống phân cấp cần được gọi. Điều này có nghĩa là chi nhánh có thể được biên dịch chứ không phải là năng động.

Ví dụ: nếu tôi có lớp không được niêm phong Animal có phương thức makeNoise(), trình biên dịch không nhất thiết biết có hay không bất kỳ cá thể Animal nào ghi đè phương thức đó. Do đó, mỗi khi một cá thể Animal gọi makeNoise(), hệ thống phân cấp lớp của cá thể cần phải được kiểm tra để xem liệu cá thể có ghi đè phương thức này trong một lớp mở rộng hay không.

Tuy nhiên, nếu tôi có lớp kín AnimalFeeder có phương thức feedAnimal(), thì trình biên dịch biết chắc chắn rằng phương pháp này không thể bị ghi đè. Nó có thể biên dịch trong một nhánh thành chương trình con hoặc lệnh tương đương thay vì sử dụng bảng công văn ảo.

Lưu ý: Bạn có thể sử dụng sealed vào một lớp học để ngăn chặn bất kỳ thừa kế từ lớp đó, và bạn có thể sử dụng sealed trên một phương pháp đã được công bố virtual trong một lớp cơ sở để ngăn chặn trọng hơn nữa của phương pháp đó.

8

Quyết định đăng các mẫu mã nhỏ để minh họa khi trình biên dịch C# phát ra lệnh "gọi" & "callvirt".

Vì vậy, đây là mã nguồn của tất cả các loại mà tôi đã sử dụng:

public sealed class SealedClass 
    { 
     public void DoSmth() 
     { } 
    } 

    public class ClassWithSealedMethod : ClassWithVirtualMethod 
    { 
     public sealed override void DoSmth() 
     { } 
    } 

    public class ClassWithVirtualMethod 
    { 
     public virtual void DoSmth() 
     { } 
    } 

Ngoài ra tôi đã một phương pháp trong đó kêu gọi tất cả các "DoSmth()" phương pháp:

public void Call() 
    { 
     SealedClass sc = new SealedClass(); 
     sc.DoSmth(); 

     ClassWithVirtualMethod cwcm = new ClassWithVirtualMethod(); 
     cwcm.DoSmth(); 

     ClassWithSealedMethod cwsm = new ClassWithSealedMethod(); 
     cwsm.DoSmth(); 
    } 

Nhìn vào "Call() "phương pháp chúng ta có thể nói rằng (về mặt lý thuyết) C# trình biên dịch nên phát ra 2" callvirt "& 1" gọi "hướng dẫn, phải không? Thật không may, thực tế là một chút khác nhau - 3 "callvirt" -s:

.method public hidebysig instance void Call() cil managed 
{ 
    .maxstack 1 
    .locals init (
     [0] class TestApp.SealedClasses.SealedClass sc, 
     [1] class TestApp.SealedClasses.ClassWithVirtualMethod cwcm, 
     [2] class TestApp.SealedClasses.ClassWithSealedMethod cwsm) 
    L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor() 
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: callvirt instance void TestApp.SealedClasses.SealedClass::DoSmth() 
    L_000c: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor() 
    L_0011: stloc.1 
    L_0012: ldloc.1 
    L_0013: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0018: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor() 
    L_001d: stloc.2 
    L_001e: ldloc.2 
    L_001f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0024: ret 
} 

Lý do khá đơn giản: thời gian chạy phải kiểm tra xem loại dụ không bằng null trước khi kêu gọi "DoSmth()" phương pháp. NHƯNG chúng tôi vẫn có thể viết mã của chúng tôi theo cách như vậy mà biên dịch C# sẽ có thể phát ra mã IL được tối ưu hóa:

public void Call() 
    { 
     new SealedClass().DoSmth(); 

     new ClassWithVirtualMethod().DoSmth(); 

     new ClassWithSealedMethod().DoSmth(); 
    } 

Kết quả là:

.method public hidebysig instance void Call() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor() 
    L_0005: call instance void TestApp.SealedClasses.SealedClass::DoSmth() 
    L_000a: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor() 
    L_000f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_0014: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor() 
    L_0019: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth() 
    L_001e: ret 
} 

Nếu bạn cố gắng gọi phi -phương pháp ảo của lớp không bị niêm kín theo cùng cách bạn cũng sẽ nhận được lệnh "gọi" thay vì "callvirt"

+0

Cảm ơn, tại sao việc kiểm tra null được tránh trong ví dụ thứ hai của bạn? Bạn có thể giải thích dùm không? –

+1

Bởi vì tôi không sử dụng biến cục bộ của kiểu "SealedClass" như trong ví dụ đầu tiên, nên trình biên dịch không cần kiểm tra xem nó có 'null' hay không. Mã IL tương tự sẽ được tạo nếu bạn khai báo phương thức "SealedClass.DoSmth()" là tĩnh –

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