อัปเดต: ตั้งแต่. NET Core 2.0 และ. NET Desktop 4.7.1 ตอนนี้ CLR รองรับการทำงานเสมือนจริง มันสามารถใช้วิธีการในชั้นเรียนที่ปิดผนึกและแทนที่การโทรเสมือนด้วยการโทรโดยตรง - และยังสามารถทำเช่นนี้สำหรับชั้นเรียนที่ไม่ได้ปิดผนึกด้วยถ้ามันสามารถรู้ได้ว่ามันปลอดภัยที่จะทำเช่นนั้น
ในกรณีเช่นนี้ (คลาสที่ปิดผนึกซึ่ง CLR ไม่สามารถตรวจจับได้ว่าปลอดภัยต่อการใช้งานเสมือนจริง) คลาสที่ปิดผนึกควรให้ประโยชน์ด้านประสิทธิภาพบางประการ
ที่กล่าวว่าฉันจะไม่คิดว่ามันจะคุ้มค่ากังวลเกี่ยวกับถ้าคุณได้ทำรหัสแล้วและระบุว่าคุณอยู่ในเส้นทางที่ร้อนแรงโดยเฉพาะอย่างยิ่งถูกเรียกว่าล้านครั้งหรือสิ่งที่:
https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/
คำตอบเดิม:
ฉันสร้างโปรแกรมทดสอบดังต่อไปนี้จากนั้นทำการ decompiled โดยใช้ Reflector เพื่อดูว่าโค้ด MSIL ถูกปล่อยออกมาอย่างไร
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");
}
ในทุกกรณีคอมไพเลอร์ C # (Visual Studio 2010 ในการกำหนดค่า Release build) ปล่อย MSIL ที่เหมือนกันซึ่งมีดังต่อไปนี้:
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
เหตุผลที่ยกมาบ่อยครั้งที่ผู้คนกล่าวว่าการปิดผนึกให้ประโยชน์ด้านประสิทธิภาพคือคอมไพเลอร์รู้ว่าคลาสนั้นไม่ได้อยู่เหนือกว่าและสามารถใช้call
แทนcallvirt
เนื่องจากไม่ต้องตรวจสอบเวอร์ชวล ฯลฯ ตามที่พิสูจน์แล้วข้างต้นนี่ไม่ใช่ จริง
ความคิดต่อไปของฉันคือว่าแม้ว่า MSIL จะเหมือนกันบางทีคอมไพเลอร์ JIT จะปฏิบัติต่อคลาสที่ปิดผนึกต่างกันหรือไม่?
ฉันวิ่งรุ่นวางจำหน่ายภายใต้ดีบักเกอร์ visual Studio และดูผลลัพธ์ x86 ที่ถอดรหัสแล้ว ในทั้งสองกรณีรหัส x86 นั้นเหมือนกันยกเว้นชื่อคลาสและที่อยู่หน่วยความจำของฟังก์ชัน (ซึ่งแน่นอนว่าจะต้องแตกต่างกัน) นี่มันคือ
// 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
ฉันคิดว่าบางทีการทำงานภายใต้โปรแกรมดีบั๊กจะทำให้การเพิ่มประสิทธิภาพก้าวร้าวน้อยลงหรือไม่
จากนั้นฉันก็รัน build release อิสระแบบสแตนด์อโลนนอกสภาพแวดล้อมการดีบักและใช้ WinDBG + SOS เพื่อบุกเข้าไปหลังจากโปรแกรมเสร็จสิ้น
ดังที่คุณเห็นจากโค้ดด้านล่างเมื่อทำงานนอกตัวดีบักคอมไพเลอร์ JIT นั้นมีความก้าวร้าวมากขึ้นและมีWriteIt
วิธีการอินไลน์ตรงเข้าไปในตัวเรียก สิ่งสำคัญคือมันเหมือนกันเมื่อเรียกคลาสที่ปิดผนึกและไม่ผนึก ไม่มีความแตกต่างใด ๆ ระหว่างชั้นเรียนที่ปิดผนึกหรือไม่ปิดผนึก
นี่คือเมื่อเรียกคลาสปกติ:
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 คลาสที่ปิดผนึก:
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
สำหรับฉันนี่เป็นข้อพิสูจน์ที่ชัดเจนว่าไม่มีการปรับปรุงประสิทธิภาพใด ๆ ระหว่างวิธีการโทรในคลาสที่ปิดผนึกและไม่ได้ปิดผนึก ... ฉันคิดว่าฉันมีความสุขในตอนนี้ :-)