Cập nhật: Kể từ .NET Core 2.0 và .NET Desktop 4.7.1, CLR hiện hỗ trợ ảo hóa. Nó có thể thực hiện các phương thức trong các lớp kín và thay thế các cuộc gọi ảo bằng các cuộc gọi trực tiếp - và nó cũng có thể thực hiện điều này cho các lớp không được niêm phong nếu nó có thể tìm ra nó an toàn để làm như vậy.
Trong trường hợp như vậy (một lớp niêm phong mà CLR không thể phát hiện là an toàn để phát hiện), một lớp kín thực sự sẽ cung cấp một số loại lợi ích hiệu suất.
Điều đó nói rằng, tôi sẽ không nghĩ rằng đáng để lo lắng trừ khi bạn đã lập hồ sơ mã và xác định rằng bạn đang ở trong một con đường đặc biệt nóng bỏng được gọi là hàng triệu lần, hoặc đại loại như thế:
https://bloss.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/
Câu trả lời gốc:
Tôi đã thực hiện chương trình thử nghiệm sau, và sau đó dịch ngược nó bằng Reflector để xem mã MSIL được phát ra.
public class NormalClass {
public void WriteIt(string x) {
Console.WriteLine("NormalClass");
Console.WriteLine(x);
}
}
public sealed class SealedClass {
public void WriteIt(string x) {
Console.WriteLine("SealedClass");
Console.WriteLine(x);
}
}
public static void CallNormal() {
var n = new NormalClass();
n.WriteIt("a string");
}
public static void CallSealed() {
var n = new SealedClass();
n.WriteIt("a string");
}
Trong mọi trường hợp, trình biên dịch C # (Visual studio 2010 trong cấu hình phát hành bản phát hành) phát ra MSIL giống hệt nhau, như sau:
L_0000: newobj instance void <NormalClass or SealedClass>::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr "a string"
L_000c: callvirt instance void <NormalClass or SealedClass>::WriteIt(string)
L_0011: ret
Lý do được trích dẫn mà mọi người nói rằng niêm phong cung cấp lợi ích hiệu năng là trình biên dịch biết rằng lớp không bị ghi đè, và do đó có thể sử dụng call
thay vìcallvirt
không phải kiểm tra ảo, v.v. Như đã chứng minh ở trên, đây không phải là thật.
Suy nghĩ tiếp theo của tôi là mặc dù MSIL giống hệt nhau, nhưng có lẽ trình biên dịch JIT xử lý các lớp niêm phong khác nhau?
Tôi đã chạy một bản phát hành dưới trình gỡ lỗi của studio trực quan và xem đầu ra x86 được dịch ngược. Trong cả hai trường hợp, mã x86 đều giống hệt nhau, ngoại trừ tên lớp và địa chỉ bộ nhớ hàm (tất nhiên phải khác nhau). Nó đây rồi
// var n = new NormalClass();
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,8
00000006 cmp dword ptr ds:[00585314h],0
0000000d je 00000014
0000000f call 70032C33
00000014 xor edx,edx
00000016 mov dword ptr [ebp-4],edx
00000019 mov ecx,588230h
0000001e call FFEEEBC0
00000023 mov dword ptr [ebp-8],eax
00000026 mov ecx,dword ptr [ebp-8]
00000029 call dword ptr ds:[00588260h]
0000002f mov eax,dword ptr [ebp-8]
00000032 mov dword ptr [ebp-4],eax
// n.WriteIt("a string");
00000035 mov edx,dword ptr ds:[033220DCh]
0000003b mov ecx,dword ptr [ebp-4]
0000003e cmp dword ptr [ecx],ecx
00000040 call dword ptr ds:[0058827Ch]
// }
00000046 nop
00000047 mov esp,ebp
00000049 pop ebp
0000004a ret
Sau đó tôi nghĩ có lẽ chạy theo trình gỡ lỗi khiến nó thực hiện tối ưu hóa ít tích cực hơn?
Sau đó, tôi đã chạy một bản phát hành độc lập có thể thực thi được bên ngoài bất kỳ môi trường gỡ lỗi nào và đã sử dụng WinDBG + SOS để đột nhập sau khi chương trình hoàn thành và xem sự phân tách mã x86 do JIT biên dịch.
Như bạn có thể thấy từ đoạn mã bên dưới, khi chạy bên ngoài trình gỡ lỗi, trình biên dịch JIT mạnh hơn và nó đã đưa WriteIt
phương thức vào thẳng trình gọi. Tuy nhiên, điều quan trọng là nó giống hệt nhau khi gọi một lớp kín và không niêm phong. Không có sự khác biệt nào giữa một lớp kín hoặc không kín.
Đây là khi gọi một lớp bình thường:
Normal JIT generated code
Begin 003c00b0, size 39
003c00b0 55 push ebp
003c00b1 8bec mov ebp,esp
003c00b3 b994391800 mov ecx,183994h (MT: ScratchConsoleApplicationFX4.NormalClass)
003c00b8 e8631fdbff call 00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c00bd e80e70106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00c2 8bc8 mov ecx,eax
003c00c4 8b1530203003 mov edx,dword ptr ds:[3302030h] ("NormalClass")
003c00ca 8b01 mov eax,dword ptr [ecx]
003c00cc 8b403c mov eax,dword ptr [eax+3Ch]
003c00cf ff5010 call dword ptr [eax+10h]
003c00d2 e8f96f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c00d7 8bc8 mov ecx,eax
003c00d9 8b1534203003 mov edx,dword ptr ds:[3302034h] ("a string")
003c00df 8b01 mov eax,dword ptr [ecx]
003c00e1 8b403c mov eax,dword ptr [eax+3Ch]
003c00e4 ff5010 call dword ptr [eax+10h]
003c00e7 5d pop ebp
003c00e8 c3 ret
Vs một lớp kín:
Normal JIT generated code
Begin 003c0100, size 39
003c0100 55 push ebp
003c0101 8bec mov ebp,esp
003c0103 b90c3a1800 mov ecx,183A0Ch (MT: ScratchConsoleApplicationFX4.SealedClass)
003c0108 e8131fdbff call 00172020 (JitHelp: CORINFO_HELP_NEWSFAST)
003c010d e8be6f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0112 8bc8 mov ecx,eax
003c0114 8b1538203003 mov edx,dword ptr ds:[3302038h] ("SealedClass")
003c011a 8b01 mov eax,dword ptr [ecx]
003c011c 8b403c mov eax,dword ptr [eax+3Ch]
003c011f ff5010 call dword ptr [eax+10h]
003c0122 e8a96f106f call mscorlib_ni+0x2570d0 (6f4c70d0) (System.Console.get_Out(), mdToken: 060008fd)
003c0127 8bc8 mov ecx,eax
003c0129 8b1534203003 mov edx,dword ptr ds:[3302034h] ("a string")
003c012f 8b01 mov eax,dword ptr [ecx]
003c0131 8b403c mov eax,dword ptr [eax+3Ch]
003c0134 ff5010 call dword ptr [eax+10h]
003c0137 5d pop ebp
003c0138 c3 ret
Đối với tôi, điều này cung cấp bằng chứng chắc chắn rằng không thể có bất kỳ cải thiện hiệu suất nào giữa các phương thức gọi trên các lớp kín và không kín ... Tôi nghĩ rằng tôi đang hạnh phúc :-)