คลาสที่ปิดผนึกมีประโยชน์ด้านประสิทธิภาพหรือไม่?


140

ฉันได้พบกับเคล็ดลับการเพิ่มประสิทธิภาพจำนวนมากที่บอกว่าคุณควรทำเครื่องหมายคลาสของคุณเป็นผนึกเพื่อรับสิทธิประโยชน์เพิ่มเติม

ฉันทำการทดสอบเพื่อตรวจสอบความแตกต่างของประสิทธิภาพและไม่พบสิ่งใดเลย ฉันกำลังทำอะไรผิดหรือเปล่า? ฉันไม่มีกรณีที่คลาสที่ปิดผนึกจะให้ผลลัพธ์ที่ดีกว่าหรือไม่

มีใครทำการทดสอบและเห็นความแตกต่าง?

ช่วยฉันเรียนรู้ :)


9
ฉันไม่คิดว่าชั้นเรียนที่ถูกผนึกมีจุดประสงค์เพื่อให้เพิ่มขึ้นอย่างสมบูรณ์ ความจริงที่ว่าพวกเขาทำอาจบังเอิญ นอกจากนั้นโปรไฟล์แอปของคุณหลังจากที่คุณได้ทำการปรับโครงสร้างใหม่เพื่อใช้คลาสที่ปิดผนึกและตรวจสอบว่ามันคุ้มค่ากับความพยายามหรือไม่ การล็อคความสามารถในการขยายตัวของคุณเพื่อทำให้การเพิ่มประสิทธิภาพขนาดเล็กที่ไม่จำเป็นจะทำให้คุณเสียค่าใช้จ่ายในระยะยาว แน่นอนถ้าคุณทำประวัติและมันช่วยให้คุณเข้าถึงมาตรฐานที่สมบูรณ์แบบของคุณ (แทนที่จะสมบูรณ์เพื่อประโยชน์ของความสมบูรณ์แบบ) จากนั้นคุณสามารถตัดสินใจเป็นทีมได้ถ้ามันคุ้มค่ากับเงินที่ใช้ไป หากคุณมีการปิดผนึกชั้นเรียนสำหรับเหตุผลที่ไม่ perf แล้วให้ em :)
เมอร์ลิน Morgan-เกรแฮม

1
คุณเคยลองด้วยการสะท้อน? ผมอ่านบางที่ instantiating จากการสะท้อนได้เร็วขึ้นกับการเรียนการปิดผนึก
onof

สำหรับความรู้ของฉันไม่มี มีการปิดผนึกด้วยเหตุผลที่แตกต่าง - เพื่อปิดกั้นการเพิ่มความสามารถซึ่งอาจมีประโยชน์ / จำเป็นในหลายกรณี การเพิ่มประสิทธิภาพประสิทธิภาพไม่ใช่เป้าหมายที่นี่
TomTom

... แต่ถ้าคุณคิดถึงคอมไพเลอร์: ถ้าคลาสของคุณถูกผนึกคุณจะรู้ที่อยู่ของวิธีการที่คุณเรียกใช้ในคลาสของคุณในเวลาคอมไพล์ หากชั้นเรียนของคุณไม่ได้รับการประทับตราคุณจะต้องแก้ไขวิธีในเวลาทำงานเนื่องจากคุณอาจต้องเรียกการแทนที่ มันแน่นอนจะมีเพียงเล็กน้อย แต่ฉันจะได้เห็นว่าจะมีบางอย่างที่แตกต่างกัน
Dan Puzey

ใช่ แต่การเรียงลำดับนั้นไม่ได้แปลไปสู่ผลประโยชน์ที่แท้จริงตามที่ OP ได้ระบุไว้ ความแตกต่างทางสถาปัตยกรรมเป็น / อาจเกี่ยวข้องมากขึ้น
TomTom

คำตอบ:


60

JITter บางครั้งจะใช้การโทรที่ไม่ใช่แบบเสมือนกับวิธีการในคลาสที่ปิดผนึกเนื่องจากไม่มีวิธีที่พวกเขาสามารถขยายเพิ่มเติมได้

มีกฎที่ซับซ้อนเกี่ยวกับประเภทการโทรเสมือน / ไม่ใช่เสมือนและฉันไม่ทราบทั้งหมดดังนั้นฉันจึงไม่สามารถจัดเค้าร่างให้คุณได้ แต่ถ้าคุณ google สำหรับคลาสที่ปิดผนึกและวิธีเสมือนคุณอาจพบบทความบางเรื่องในหัวข้อ

โปรดทราบว่าประสิทธิภาพการทำงานประเภทใดก็ตามที่คุณจะได้รับจากการเพิ่มประสิทธิภาพระดับนี้ควรได้รับการพิจารณาว่าเป็นทางเลือกสุดท้ายให้ปรับระดับของอัลกอริทึมให้ดีที่สุดเสมอ

นี่คือลิงค์เดียวที่กล่าวถึงสิ่งนี้: การค้นหาคำหลักที่ปิดผนึก


2
ลิงค์ 'rambling' นั้นน่าสนใจเพราะดูเหมือนว่าเป็นเรื่องดีด้านเทคนิค แต่จริงๆแล้วเป็นเรื่องไร้สาระ อ่านความคิดเห็นในบทความสำหรับข้อมูลเพิ่มเติม สรุป: 3 เหตุผลที่ได้รับคือการกำหนดประสิทธิภาพและความปลอดภัย / การคาดการณ์ - [ดูความคิดเห็นถัดไป]
Steven A. Lowe

1
[ดำเนินการต่อ] การกำหนดเวอร์ชันจะใช้เฉพาะเมื่อไม่มีคลาสย่อย, duh, แต่ขยายอาร์กิวเมนต์นี้ไปยังทุกคลาสและทันใดนั้นคุณไม่มีการสืบทอดและเดาว่าอะไร, ภาษานั้นจะไม่เชิงวัตถุอีกต่อไป (แต่เป็นเพียงวัตถุ)! [ดูต่อไป]
Steven A. Lowe

3
[ต่อ] ตัวอย่างประสิทธิภาพเป็นเรื่องตลก: การปรับการเรียกใช้เมธอดเสมือน ทำไมคลาสที่ถูกผนึกจะมีเมธอดเสมือนตั้งแต่แรกเนื่องจากมันไม่สามารถซับคลาสได้ ในที่สุดอาร์กิวเมนต์ Security / Predictability นั้นชัดเจนว่า: 'คุณไม่สามารถใช้เพื่อให้ปลอดภัย / คาดเดาได้' ฮ่า ๆ!
Steven A. Lowe

16
@Steven A. Lowe - ฉันคิดว่า Jeffrey Richter กำลังพยายามพูดในลักษณะวงเวียนเล็กน้อยคือถ้าคุณออกจากชั้นเรียนของคุณคุณต้องคิดว่าคลาสที่ได้มานั้นสามารถ / จะใช้อย่างไรและถ้าคุณไม่มี เวลาหรือความชอบในการทำสิ่งนี้อย่างถูกต้องจากนั้นจึงปิดผนึกเนื่องจากมีโอกาสน้อยที่จะทำให้เกิดการเปลี่ยนแปลงในรหัสของผู้อื่นในอนาคต นั่นไม่ใช่เรื่องไร้สาระเลยมันเป็นสามัญสำนึกที่ดี
Greg Beech

6
คลาสที่ปิดผนึกอาจมีวิธีเสมือนเนื่องจากอาจมาจากคลาสที่ประกาศ เมื่อคุณประกาศในภายหลังให้ประกาศตัวแปรของคลาสผู้สืบทอดที่ถูกผนึกและเรียกใช้เมธอดนั้นคอมไพเลอร์อาจส่งการเรียกโดยตรงไปยังการใช้งานที่รู้จักเนื่องจากมันรู้ว่าไม่มีวิธีที่จะแตกต่างจากสิ่งที่อยู่ในที่รู้จัก - vtable ณ เวลารวบรวมสำหรับคลาสนั้น สำหรับการปิดผนึก / การปิดผนึกนั่นเป็นการสนทนาที่แตกต่างกันและฉันเห็นด้วยกับเหตุผลที่ทำให้การปิดผนึกชั้นเรียนเป็นค่าเริ่มต้น
Lasse V. Karlsen

143

คำตอบคือไม่ชั้นเรียนที่ปิดสนิทจะทำงานได้ดีกว่าที่ไม่ได้ปิดผนึก

ปัญหาเกิดขึ้นcallกับcallvirtรหัส op ของvs IL Callเร็วกว่าcallvirtและใช้callvirtเป็นหลักเมื่อคุณไม่รู้ว่าวัตถุนั้นมีคลาสย่อยหรือไม่ ดังนั้นผู้คนคิดว่าถ้าคุณประทับตราคลาสทั้งหมดรหัส op จะเปลี่ยนจากcalvirtsเป็นcallsและจะเร็วขึ้น

น่าเสียดายที่callvirtสิ่งอื่น ๆ นั้นมีประโยชน์เช่นกันเช่นการตรวจสอบการอ้างอิงที่เป็นโมฆะ ซึ่งหมายความว่าแม้ว่าชั้นเรียนจะถูกปิดผนึกการอ้างอิงอาจยังคงว่างcallvirtเปล่าดังนั้นจึงจำเป็นต้องใช้ คุณสามารถหลีกเลี่ยงสิ่งนี้ได้ (โดยไม่จำเป็นต้องปิดผนึกชั้นเรียน) แต่มันจะกลายเป็นเรื่องไร้สาระ

โครงสร้างใช้callเพราะพวกเขาไม่สามารถ subclassed และไม่เคยเป็นโมฆะ

ดูคำถามนี้สำหรับข้อมูลเพิ่มเติม:

โทรและ callvirt


5
AFAIK สถานการณ์ที่callใช้คือ: ในสถานการณ์new T().Method()สำหรับstructวิธีการสำหรับการโทรไปยังvirtualวิธีการที่ไม่ใช่เสมือน(เช่นbase.Virtual()) หรือสำหรับstaticวิธีการ callvirtการใช้งานทุกที่อื่น
porges

1
อืม ... ฉันรู้ว่านี่เก่า แต่ก็ไม่ถูกต้อง ... ชัยชนะอันยิ่งใหญ่ของคลาสปิดผนึกคือเมื่อ JIT Optimizer สามารถอินไลน์การโทร ... ในกรณีนี้คลาสที่ปิดผนึกอาจเป็นชัยชนะที่ยิ่งใหญ่ .
Brian Kennedy

5
ทำไมคำตอบนี้จึงผิด จาก Mono changelog: "การเพิ่มประสิทธิภาพเสมือนจริงสำหรับคลาสและวิธีการที่ปิดผนึกการปรับปรุงประสิทธิภาพ pystone IronPython 2.0 4% โปรแกรมอื่น ๆ สามารถคาดหวังการปรับปรุงที่คล้ายกัน [Rodrigo]" คลาสที่ปิดผนึกสามารถปรับปรุงประสิทธิภาพได้ แต่เช่นเคยขึ้นอยู่กับสถานการณ์
Smilediver

1
@Smileiver มันสามารถปรับปรุงประสิทธิภาพ แต่ถ้าคุณมี JIT ที่ไม่ดี (ไม่รู้เลยว่า NET JIT นั้นดีแค่ไหนในทุกวันนี้ - เคยเป็นสิ่งที่แย่มากในเรื่องนั้น) ตัวอย่างเช่นฮอตสปอตจะ inline โทรเสมือนและ deoptimize ในภายหลังหากจำเป็น - ดังนั้นคุณจ่ายค่าใช้จ่ายเพิ่มเติมเฉพาะในกรณีที่คุณจริงคลาสย่อยคลาส (และยังไม่จำเป็นต้อง)
Voo

1
-1 JIT ไม่จำเป็นต้องสร้างรหัสเครื่องเดียวกันสำหรับ IL opcodes เดียวกัน การตรวจสอบ Null และการโทรเสมือนเป็นขั้นตอนแบบฉากและแยกกันของ callvirt ในกรณีของประเภทที่ปิดผนึกคอมไพเลอร์ JIT ยังสามารถเพิ่มประสิทธิภาพส่วนหนึ่งของ callvirt สิ่งเดียวกันจะเกิดขึ้นเมื่อคอมไพเลอร์ JIT สามารถรับประกันได้ว่าการอ้างอิงจะไม่เป็นโมฆะ
rightfold

29

อัปเดต: ตั้งแต่. 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

สำหรับฉันนี่เป็นข้อพิสูจน์ที่ชัดเจนว่าไม่มีการปรับปรุงประสิทธิภาพใด ๆ ระหว่างวิธีการโทรในคลาสที่ปิดผนึกและไม่ได้ปิดผนึก ... ฉันคิดว่าฉันมีความสุขในตอนนี้ :-)


เหตุผลใดที่คุณโพสต์คำตอบนี้สองครั้งแทนที่จะปิดอีกเป็นซ้ำ พวกเขาดูเหมือนซ้ำกับฉันแม้ว่าจะไม่ใช่พื้นที่ของฉัน
เฟล็กโซ

1
จะเกิดอะไรขึ้นถ้าวิธีการนั้นยาวกว่า (รหัส IL มากกว่า 32 ไบต์) เพื่อหลีกเลี่ยงการทำอินไลน์และดูว่าการดำเนินการเรียกใดจะใช้ หากมีการขีดเส้นใต้คุณจะไม่เห็นสายดังนั้นจึงไม่สามารถตัดสินผลกระทบได้
ygoe

ฉันสับสน: ทำไมถึงมีcallvirtเมื่อวิธีการเหล่านี้ไม่ได้เริ่มต้นด้วยเสมือน?
ประหลาด

@ ประหลาดใจฉันจำไม่ได้ว่าฉันเห็นสิ่งนี้ แต่ฉันอ่านว่า CLR ใช้callvirtสำหรับวิธีการปิดผนึกเพราะยังต้องตรวจสอบวัตถุก่อนที่จะเรียกการเรียกใช้เมธอดและเมื่อคุณคำนึงถึงสิ่งนั้นคุณก็อาจจะแค่ callvirtใช้ หากต้องการลบcallvirtและเพียงกระโดดโดยตรงพวกเขาอาจต้องแก้ไข C # เพื่ออนุญาต((string)null).methodCall()เช่นเดียวกับ C ++ หรือพวกเขาต้องการพิสูจน์แบบคงที่ว่าวัตถุนั้นไม่ว่าง (ซึ่งพวกเขาสามารถทำได้ แต่ไม่ได้ใส่ใจ)
Orion Edwards

1
อุปกรณ์สำหรับจะพยายามขุดลงไปถึงระดับรหัสเครื่อง แต่จะมากงบทำอย่างระมัดระวังเช่น 'นี้ให้หลักฐานที่มั่นคงว่ามีไม่สามารถที่จะปรับปรุงประสิทธิภาพการทำงานใด ๆ สิ่งที่คุณแสดงให้เห็นคือสำหรับสถานการณ์หนึ่งโดยเฉพาะไม่มีความแตกต่างในเอาต์พุตดั้งเดิม เป็นจุดข้อมูลหนึ่งจุดและไม่สามารถสรุปได้ว่าเป็นภาพรวมของทุกสถานการณ์ สำหรับผู้เริ่มต้นคลาสของคุณไม่ได้กำหนดวิธีเสมือนใด ๆ ดังนั้นจึงไม่จำเป็นต้องมีการโทรเสมือน
Matt Craig

24

อย่างที่ฉันรู้ไม่มีการรับประกันผลประโยชน์ด้านประสิทธิภาพ แต่มีโอกาสที่จะลดการปรับโทษตามเงื่อนไขบางอย่างด้วยวิธีการปิดผนึก (คลาสที่ปิดผนึกทำให้วิธีการทั้งหมดถูกปิดผนึก)

แต่มันก็ขึ้นอยู่กับการใช้งานคอมไพเลอร์และสภาพแวดล้อมการดำเนินการ


รายละเอียด

ซีพียูสมัยใหม่หลายแห่งใช้โครงสร้างของไพพ์ไลน์แบบยาวเพื่อเพิ่มประสิทธิภาพ เนื่องจาก CPU เร็วกว่าหน่วยความจำอย่างไม่น่าเชื่อ CPU จึงต้องดึงรหัสล่วงหน้าจากหน่วยความจำเพื่อเร่งความเร็ว หากรหัสไม่พร้อมในเวลาที่เหมาะสมท่อจะว่าง

มีอุปสรรคใหญ่ที่เรียกว่าการจัดส่งแบบไดนามิกซึ่งขัดขวางการเพิ่มประสิทธิภาพ 'prefetching' นี้ คุณสามารถเข้าใจสิ่งนี้เป็นเพียงการแตกแขนงตามเงื่อนไข

// Value of `v` is unknown,
// and can be resolved only at runtime.
// CPU cannot know which code to prefetch.
// Therefore, just prefetch any one of a() or b().
// This is *speculative execution*.
int v = random();
if (v==1) a();
else b();

CPU ไม่สามารถดึงรหัสถัดไปเพื่อเรียกใช้งานในกรณีนี้ได้เนื่องจากไม่ทราบตำแหน่งรหัสถัดไปจนกว่าจะแก้ไขเงื่อนไขได้ ดังนั้นสิ่งนี้ทำให้เกิดอันตรายทำให้ท่อไม่ได้ทำงาน และการลงโทษด้วยการไม่ได้ใช้งานนั้นมีขนาดใหญ่เป็นปกติ

สิ่งที่คล้ายกันเกิดขึ้นในกรณีที่วิธีการเอาชนะ คอมไพเลอร์อาจกำหนดวิธีการที่เหมาะสมแทนการเรียกเมธอดปัจจุบัน แต่บางครั้งก็เป็นไปไม่ได้ ในกรณีนี้วิธีการที่เหมาะสมสามารถพิจารณาได้เฉพาะตอนรันไทม์ นี่เป็นกรณีของการจัดส่งแบบไดนามิกและเหตุผลหลักของภาษาที่พิมพ์แบบไดนามิกมักจะช้ากว่าภาษาที่พิมพ์แบบคงที่

CPU บางตัว (รวมถึงชิป x86 ของ Intel ที่ใช้ล่าสุด) ใช้เทคนิคที่เรียกว่าการประมวลผลแบบเก็งกำไรเพื่อใช้ไปป์ไลน์แม้ในสถานการณ์ เพียงดึงหนึ่งในเส้นทางการดำเนินการ แต่อัตราการเข้าชมของเทคนิคนี้ไม่สูงมากนัก และความล้มเหลวในการเก็งกำไรทำให้เกิดช่องทางเดินท่อซึ่งทำให้เสียประสิทธิภาพอย่างมาก (นี่คือการใช้งาน CPU อย่างสมบูรณ์บางซีพียูมือถือบางคนรู้จักกันดีว่าไม่เหมาะสำหรับการประหยัดพลังงาน)

โดยทั่วไป C # เป็นภาษาที่รวบรวมแบบคงที่ แต่ไม่เสมอไป. ฉันไม่ทราบเงื่อนไขที่แน่นอนและทั้งหมดนี้ขึ้นอยู่กับการใช้งานคอมไพเลอร์ sealedคอมไพเลอร์บางคนสามารถขจัดความเป็นไปได้ของการจัดส่งแบบไดนามิกโดยการป้องกันวิธีการเอาชนะถ้าวิธีการที่ถูกทำเครื่องหมายเป็น คอมไพเลอร์โง่อาจไม่ sealedนี่คือผลประโยชน์ของ


คำตอบนี้ ( ทำไมการประมวลผลอาร์เรย์ที่เรียงลำดับเร็วกว่าอาร์เรย์ที่ไม่เรียงลำดับเร็วกว่า ) จึงอธิบายการทำนายสาขาได้ดีกว่ามาก


1
CPU ระดับ Pentium ดึงข้อมูลการจัดส่งทางอ้อมโดยตรง บางครั้งการเปลี่ยนเส้นทางของตัวชี้ฟังก์ชั่นจะเร็วกว่าถ้าไม่สามารถคาดเดาได้ด้วยเหตุผลนี้
Joshua

2
ข้อดีอย่างหนึ่งของฟังก์ชั่นที่ไม่เสมือนหรือปิดผนึกคือพวกเขาสามารถ inline ในสถานการณ์มากขึ้น
CodesInChaos

4

<ปิดหัวข้อพูดจาโผงผาง>

ฉันเกลียดชั้นเรียนที่ปิดสนิท แม้ว่าประโยชน์ด้านประสิทธิภาพนั้นน่าประหลาดใจ (ซึ่งฉันสงสัย) พวกมันทำลายโมเดลเชิงวัตถุโดยป้องกันการใช้ซ้ำผ่านการสืบทอด ตัวอย่างเช่นคลาส Thread ถูกปิดผนึก ในขณะที่ฉันเห็นว่าอาจต้องการเธรดให้มีประสิทธิภาพมากที่สุดเท่าที่จะเป็นไปได้ แต่ฉันสามารถจินตนาการถึงสถานการณ์ที่ความสามารถในการซับคลาสเธรดจะได้รับประโยชน์อย่างมาก ผู้เขียนคลาสหากคุณต้องประทับตราชั้นเรียนของคุณด้วยเหตุผล "ประสิทธิภาพ" โปรดระบุส่วนต่อประสานอย่างน้อยที่สุดเพื่อให้เราไม่ต้องห่อและแทนที่ทุกที่ที่เราต้องการคุณสมบัติที่คุณลืม

ตัวอย่าง: SafeThreadต้องล้อมคลาสของเธรดเนื่องจาก Thread ถูกปิดผนึกและไม่มีส่วนต่อประสาน IThread SafeThread ดักจับข้อยกเว้นที่ไม่สามารถจัดการกับเธรดได้โดยอัตโนมัติมีบางอย่างขาดหายไปจากคลาสเธรด [และไม่ใช่เหตุการณ์ข้อยกเว้นที่ไม่สามารถจัดการได้จะไม่รับข้อยกเว้นที่ไม่ได้จัดการในเธรดรอง]

</ ปิดหัวข้อพูดจาโผงผาง>


35
ฉันไม่ได้ปิดคลาสเพื่อเหตุผลด้านประสิทธิภาพ ฉันปิดผนึกด้วยเหตุผลด้านการออกแบบ การออกแบบสำหรับการสืบทอดนั้นยากและความพยายามนั้นจะสูญเปล่าเป็นส่วนใหญ่ ฉันทั้งหมดเห็นด้วยเกี่ยวกับการให้การเชื่อมต่อแม้ว่า - ที่ไกลทางออกที่ดีกว่าการเรียนผนึก
Jon Skeet

6
การห่อหุ้มโดยทั่วไปเป็นทางออกที่ดีกว่าการสืบทอด ในการใช้ตัวอย่างของเธรดที่เฉพาะเจาะจงการดักข้อยกเว้นเธรดจะทำลายหลักการการทดแทน Liskov เนื่องจากคุณได้เปลี่ยนพฤติกรรมที่เป็นเอกสารของคลาสเธรดดังนั้นแม้ว่าคุณจะได้รับมาจากมันก็ไม่สมเหตุสมผลที่จะบอกว่าคุณสามารถใช้ SafeThread ได้ทุกที่ คุณสามารถใช้เธรด ในกรณีนี้คุณควรที่จะแค็ปซูลเธรดในคลาสอื่นซึ่งมีพฤติกรรมที่แตกต่างกันซึ่งคุณสามารถทำได้ บางครั้งสิ่งต่าง ๆ ได้รับการผนึกเพื่อประโยชน์ของคุณเอง
Greg Beech

1
@ [Greg Beech]: ความเห็นไม่ใช่ความจริง - ความสามารถในการสืบทอดจาก Thread เพื่อแก้ไขการกำกับดูแลที่เลวร้ายในการออกแบบไม่ใช่สิ่งที่เลวร้าย ;-) และฉันคิดว่าคุณพูดเกินจริง LSP - คุณสมบัติที่น่าสังเวช q (x) ใน กรณีนี้คือ 'มีข้อยกเว้น unhandled ทำลายโปรแกรม' ซึ่งไม่ได้เป็น "ทรัพย์สินที่พึงประสงค์" :-)
Steven A. Lowe

1
ไม่ แต่ฉันมีรหัสเส็งเคร็งที่ฉันเปิดใช้งานนักพัฒนารายอื่น ๆ เพื่อละเมิดข้อมูลของฉันโดยไม่ปิดผนึกหรืออนุญาตให้ใช้ตัวพิมพ์เล็ก รหัสของฉันส่วนใหญ่ในปัจจุบันคือการยืนยันและสัญญาอื่น ๆ ที่เกี่ยวข้อง และฉันค่อนข้างเปิดเผยเกี่ยวกับความจริงที่ว่าฉันทำเช่นนี้เพื่อเจ็บปวดใน ***
ทัวริงเสร็จสมบูรณ์

2
เนื่องจากเรากำลังพูดจาโผงผางนอกหัวข้อที่นี่เช่นเดียวกับที่คุณเกลียดชั้นเรียนที่ปิดสนิทฉันเกลียดที่จะกลืนข้อยกเว้น ไม่มีอะไรจะเลวร้ายไปกว่าเมื่อมีบางสิ่งเริ่มต้นที่จะอัปโหลด แต่โปรแกรมดำเนินการต่อ JavaScript เป็นที่ชื่นชอบ คุณทำการเปลี่ยนแปลงรหัสบางส่วนและการคลิกปุ่มก็ไม่ได้ทำสิ่งใดเลย ที่ดี! ASP.NET และ UpdatePanel เป็นอีกอันหนึ่ง อย่างจริงจังถ้าตัวจัดการปุ่มของฉันขว้างมันเป็นเรื่องใหญ่และมันจำเป็นต้องผิดพลาดเพื่อที่ฉันจะรู้ว่ามีบางอย่างที่ต้องแก้ไข! ปุ่มที่ไม่ทำอะไรเลยเป็นมากขึ้นไร้ประโยชน์นอกเหนือจากปุ่มที่นำขึ้นหน้าจอความผิดพลาด!
Roman Starkov

4

การทำเครื่องหมายคลาสsealedควรไม่มีผลกระทบต่อประสิทธิภาพ

มีหลายกรณีที่cscอาจต้องปล่อยcallvirtopcode แทนcallopcode อย่างไรก็ตามดูเหมือนว่ากรณีเหล่านี้จะหายาก

และสำหรับฉันแล้วดูเหมือนว่า JIT ควรจะสามารถเรียกฟังก์ชั่นที่ไม่ใช่แบบเสมือนที่เรียกcallvirtได้callถ้ามันรู้ว่าคลาสนั้นไม่มีคลาสย่อย (เลย) ถ้ามีวิธีการใช้งานเพียงวิธีเดียวไม่มีจุดโหลดที่อยู่ของมันจาก vtable - เพียงแค่เรียกใช้งานหนึ่งโดยตรง สำหรับเรื่องนั้น JIT ยังสามารถอินไลน์ฟังก์ชันได้

มันเป็นบิตของการเล่นการพนันในส่วนของ JIT เพราะถ้าเป็น subclass จะโหลดต่อมา JIT จะต้องทิ้งรหัสเครื่องที่และรวบรวมรหัสอีกครั้งเปล่งโทรเสมือนจริง ฉันเดาว่านี่ไม่ได้เกิดขึ้นบ่อยครั้งในทางปฏิบัติ

(และใช่แล้วนักออกแบบ VM จะทำสิ่งเล็ก ๆ น้อย ๆ เหล่านี้เพื่อให้ได้รับชัยชนะอย่างจริงจัง)


3

คลาสที่ปิดผนึกควรจัดเตรียมการปรับปรุงประสิทธิภาพ เนื่องจากคลาสที่ปิดผนึกไม่สามารถรับสมาชิกเสมือนใด ๆ สามารถเปลี่ยนเป็นสมาชิกที่ไม่ใช่เสมือนได้

แน่นอนว่าเรากำลังพูดถึงกำไรเล็กน้อย ฉันจะไม่ทำเครื่องหมายชั้นเรียนเป็นผนึกเพื่อรับการปรับปรุงประสิทธิภาพเว้นแต่การทำโปรไฟล์เปิดเผยว่าเป็นปัญหา


พวกเขาควรแต่ดูเหมือนว่าพวกเขาไม่ได้ หาก CLR มีแนวคิดประเภทการอ้างอิงที่ไม่เป็นโมฆะคลาสที่ปิดผนึกจะดีกว่าเนื่องจากคอมไพเลอร์สามารถปล่อยcallแทนcallvirt... ฉันชอบประเภทการอ้างอิงที่ไม่เป็นโมฆะด้วยเหตุผลอื่นอีกมากมาย ... ถอนหายใจ :-(
Orion Edwards

3

ฉันพิจารณาชั้นเรียน "ที่ปิดสนิท" ในกรณีปกติและฉันมักจะมีเหตุผลที่จะละเว้นคำหลักที่ "ปิดผนึก"

เหตุผลที่สำคัญที่สุดสำหรับฉันคือ:

a) การตรวจสอบเวลาการคอมไพล์ที่ดีขึ้น (การส่งไปยังอินเตอร์เฟสที่ไม่ได้นำไปใช้จะถูกตรวจพบในเวลารวบรวมไม่ใช่เฉพาะตอนรันไทม์)

และเหตุผลสำคัญที่สุด:

b) การใช้ชั้นเรียนของฉันในทางที่ผิดนั้นเป็นไปไม่ได้

ฉันหวังว่า Microsoft จะทำให้ "มาตรฐาน" เป็นมาตรฐานไม่ใช่ "ปิดผนึก"


ฉันเชื่อว่า "ละเว้น" (เพื่อออกไป) ควรเป็น "ปล่อย" (เพื่อผลิต) หรือไม่
2864740

2

@Vaibhav คุณทำการทดสอบประเภทใดเพื่อวัดประสิทธิภาพ

ฉันเดาว่าจะต้องใช้Rotorและเจาะเข้า CLI และเข้าใจว่าคลาสที่ปิดผนึกจะปรับปรุงประสิทธิภาพได้อย่างไร

SSCLI (โรเตอร์)
SSCLI: โครงสร้างพื้นฐานภาษากลางที่ใช้ร่วมกันที่มา

Common Language Infrastructure (CLI) เป็นมาตรฐาน ECMA ที่อธิบายถึงแกนกลางของ. NET Framework Shared Source CLI (SSCLI) หรือที่เรียกว่า Rotor เป็นไฟล์เก็บถาวรที่บีบอัดของซอร์สโค้ดเพื่อการใช้งานของ ECMA CLI และข้อกำหนดภาษา ECMA C # ซึ่งเป็นหัวใจสำคัญของสถาปัตยกรรม. NET ของ Microsoft


การทดสอบรวมถึงการสร้างลำดับชั้นของชั้นเรียนด้วยวิธีการบางอย่างที่ทำงานแบบจำลอง (การจัดการสตริงส่วนใหญ่) บางส่วนของวิธีการเหล่านี้เป็นเสมือน พวกเขาโทรหากันที่นี่และที่นั่น จากนั้นเรียกวิธีการเหล่านี้ 100, 10,000 และ 100000 ครั้ง ... และการวัดเวลาที่ผ่านไป จากนั้นเรียกใช้สิ่งเหล่านี้หลังจากทำเครื่องหมายชั้นเรียนเป็นปิดผนึก และวัดอีกครั้ง ไม่มีความแตกต่างในพวกเขา
Vaibhav

2

คลาสที่ถูกปิดผนึกจะเร็วกว่าอย่างน้อยนิด แต่บางครั้งก็อาจจะเร็วกว่านี้ ... หาก JIT Optimizer สามารถใช้การโทรแบบอินไลน์ซึ่งจะเป็นการโทรเสมือน ดังนั้นที่มีวิธีการที่เรียกว่ามีขนาดเล็กพอที่จะ inline แน่นอนพิจารณาผนึกชั้น

อย่างไรก็ตามเหตุผลที่ดีที่สุดในการปิดชั้นเรียนคือการพูดว่า "ฉันไม่ได้ออกแบบสิ่งนี้เพื่อสืบทอดจากดังนั้นฉันจะไม่ปล่อยให้คุณถูกเผาโดยสมมติว่ามันถูกออกแบบมาให้เป็นเช่นนั้นและฉันจะไม่ไป เพื่อเผาตัวฉันเองโดยการล็อคการใช้งานเพราะฉันให้คุณได้มาจากมัน "

ฉันรู้ว่าที่นี่มีบางคนกล่าวว่าพวกเขาเกลียดชั้นเรียนที่ปิดสนิทเพราะพวกเขาต้องการโอกาสที่จะได้รับจากสิ่งใด ... แต่นั่นไม่ใช่ทางเลือกที่รักษาได้มากที่สุด ... เพราะการเปิดเผยชั้นเรียนเพื่อให้ได้มา ที่. มันคล้ายกับว่า "ฉันเกลียดชั้นเรียนที่มีสมาชิกส่วนตัว ... ฉันไม่สามารถทำให้ชั้นเรียนทำในสิ่งที่ฉันต้องการได้เพราะฉันไม่มีสิทธิ์เข้าถึง" การห่อหุ้มเป็นสิ่งสำคัญ ... การปิดผนึกเป็นรูปแบบหนึ่งของการห่อหุ้ม


คอมไพเลอร์ C # จะยังคงใช้callvirt(การเรียกใช้เมธอดเสมือน) สำหรับเมธอดอินสแตนซ์บนคลาสที่ปิดผนึกเนื่องจากยังต้องทำการตรวจสอบวัตถุว่างบนพวกเขา เกี่ยวกับการ inline, CLR JIT สามารถ (และไม่) วิธีการแบบอินไลน์เสมือนเรียกร้องให้ทั้งชั้นเรียนที่ปิดสนิทและไม่ปิดผนึก ... ดังนั้นใช่ สิ่งที่มีประสิทธิภาพเป็นตำนาน
Orion Edwards

1

หากต้องการดูพวกเขาจริงๆคุณต้องวิเคราะห์coded JIT ที่คอมไพล์แล้ว (อันสุดท้าย)

รหัส C #

public sealed class Sealed
{
    public string Message { get; set; }
    public void DoStuff() { }
}
public class Derived : Base
{
    public sealed override void DoStuff() { }
}
public class Base
{
    public string Message { get; set; }
    public virtual void DoStuff() { }
}
static void Main()
{
    Sealed sealedClass = new Sealed();
    sealedClass.DoStuff();
    Derived derivedClass = new Derived();
    derivedClass.DoStuff();
    Base BaseClass = new Base();
    BaseClass.DoStuff();
}

รหัส MIL

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       41 (0x29)
  .maxstack  8
  IL_0000:  newobj     instance void ConsoleApp1.Program/Sealed::.ctor()
  IL_0005:  callvirt   instance void ConsoleApp1.Program/Sealed::DoStuff()
  IL_000a:  newobj     instance void ConsoleApp1.Program/Derived::.ctor()
  IL_000f:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0014:  newobj     instance void ConsoleApp1.Program/Base::.ctor()
  IL_0019:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
  IL_0028:  ret
} // end of method Program::Main

JIT- รหัสที่คอมไพล์แล้ว

--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
        {
0066084A  in          al,dx  
0066084B  push        edi  
0066084C  push        esi  
0066084D  push        ebx  
0066084E  sub         esp,4Ch  
00660851  lea         edi,[ebp-58h]  
00660854  mov         ecx,13h  
00660859  xor         eax,eax  
0066085B  rep stos    dword ptr es:[edi]  
0066085D  cmp         dword ptr ds:[5842F0h],0  
00660864  je          0066086B  
00660866  call        744CFAD0  
0066086B  xor         edx,edx  
0066086D  mov         dword ptr [ebp-3Ch],edx  
00660870  xor         edx,edx  
00660872  mov         dword ptr [ebp-48h],edx  
00660875  xor         edx,edx  
00660877  mov         dword ptr [ebp-44h],edx  
0066087A  xor         edx,edx  
0066087C  mov         dword ptr [ebp-40h],edx  
0066087F  nop  
            Sealed sealedClass = new Sealed();
00660880  mov         ecx,584E1Ch  
00660885  call        005730F4  
0066088A  mov         dword ptr [ebp-4Ch],eax  
0066088D  mov         ecx,dword ptr [ebp-4Ch]  
00660890  call        00660468  
00660895  mov         eax,dword ptr [ebp-4Ch]  
00660898  mov         dword ptr [ebp-3Ch],eax  
            sealedClass.DoStuff();
0066089B  mov         ecx,dword ptr [ebp-3Ch]  
0066089E  cmp         dword ptr [ecx],ecx  
006608A0  call        00660460  
006608A5  nop  
            Derived derivedClass = new Derived();
006608A6  mov         ecx,584F3Ch  
006608AB  call        005730F4  
006608B0  mov         dword ptr [ebp-50h],eax  
006608B3  mov         ecx,dword ptr [ebp-50h]  
006608B6  call        006604A8  
006608BB  mov         eax,dword ptr [ebp-50h]  
006608BE  mov         dword ptr [ebp-40h],eax  
            derivedClass.DoStuff();
006608C1  mov         ecx,dword ptr [ebp-40h]  
006608C4  mov         eax,dword ptr [ecx]  
006608C6  mov         eax,dword ptr [eax+28h]  
006608C9  call        dword ptr [eax+10h]  
006608CC  nop  
            Base BaseClass = new Base();
006608CD  mov         ecx,584EC0h  
006608D2  call        005730F4  
006608D7  mov         dword ptr [ebp-54h],eax  
006608DA  mov         ecx,dword ptr [ebp-54h]  
006608DD  call        00660490  
006608E2  mov         eax,dword ptr [ebp-54h]  
006608E5  mov         dword ptr [ebp-44h],eax  
            BaseClass.DoStuff();
006608E8  mov         ecx,dword ptr [ebp-44h]  
006608EB  mov         eax,dword ptr [ecx]  
006608ED  mov         eax,dword ptr [eax+28h]  
006608F0  call        dword ptr [eax+10h]  
006608F3  nop  
        }
0066091A  nop  
0066091B  lea         esp,[ebp-0Ch]  
0066091E  pop         ebx  
0066091F  pop         esi  
00660920  pop         edi  
00660921  pop         ebp  

00660922  ret  

ในขณะที่การสร้างวัตถุเหมือนกันคำสั่งที่ดำเนินการเพื่อเรียกใช้วิธีการของคลาสที่ปิดผนึกและได้รับ / ฐานแตกต่างกันเล็กน้อย หลังจากย้ายข้อมูลไปยังรีจิสเตอร์หรือ RAM (คำสั่ง mov) การเรียกใช้เมธอดที่ปิดผนึกดำเนินการเปรียบเทียบระหว่าง dword ptr [ecx], ecx (คำสั่ง cmp) จากนั้นเรียกใช้เมธอดในขณะที่คลาสที่ได้รับ / ฐานดำเนินการโดยตรง .

ตามรายงานที่เขียนโดยTorbj¨orn Granlund คำสั่งเวลาแฝงและปริมาณงานสำหรับโปรเซสเซอร์ AMD และ Intel x86ความเร็วของคำสั่งต่อไปนี้ใน Intel Pentium 4 คือ:

  • MOV : มี 1 รอบเป็นเวลาแฝงและตัวประมวลผลสามารถรองรับ 2.5 คำแนะนำต่อรอบประเภทนี้
  • cmp : มี 1 รอบเป็นเวลาแฝงและตัวประมวลผลสามารถรักษา 2 คำแนะนำต่อรอบประเภทนี้

ลิงค์ : https://gmplib.org/~tege/x86-timing.pdf

นี่หมายถึงว่านึกคิดเวลาที่จำเป็นในการเรียกใช้วิธีการปิดผนึกเป็น 2 รอบในขณะที่เวลาที่จำเป็นในการเรียกใช้วิธีการเรียนที่ได้มาหรือฐานเป็น 3 รอบ

การเพิ่มประสิทธิภาพของคอมไพเลอร์สร้างความแตกต่างระหว่างการแสดงของคลาสที่ปิดผนึกและไม่ปิดผนึกต่ำจนเราพูดถึงวงการโปรเซสเซอร์และด้วยเหตุนี้จึงไม่เกี่ยวข้องกับแอปพลิเคชั่นส่วนใหญ่


-10

เรียกใช้รหัสนี้และคุณจะเห็นว่าคลาสที่ปิดผนึกนั้นเร็วกว่า 2 เท่า:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new SealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("Sealed class : {0}", watch.Elapsed.ToString());

        watch.Start();
        for (int i = 0; i < 10000000; i++)
        {
            new NonSealedClass().GetName();
        }
        watch.Stop();
        Console.WriteLine("NonSealed class : {0}", watch.Elapsed.ToString());

        Console.ReadKey();
    }
}

sealed class SealedClass
{
    public string GetName()
    {
        return "SealedClass";
    }
}

class NonSealedClass
{
    public string GetName()
    {
        return "NonSealedClass";
    }
}

เอาต์พุต: คลาสปิดผนึก: 00: 00: 00.1897568 คลาสที่ไม่ปิดผนึก: 00: 00: 00.3826678


9
มีปัญหาสองสามข้อในเรื่องนี้ ก่อนอื่นคุณไม่ได้รีเซ็ตนาฬิกาจับเวลาระหว่างการทดสอบครั้งแรกและครั้งที่สอง ประการที่สองวิธีที่คุณใช้ในการเรียกใช้หมายความว่ารหัส op ทั้งหมดจะถูกเรียกไม่ใช่ callvirt ดังนั้นประเภทนั้นไม่สำคัญ
Cameron MacFarland

1
RuslanG คุณลืมโทร watch.Reset () หลังจากทำการทดสอบครั้งแรก o_O:]

ใช่รหัสข้างต้นมีข้อบกพร่อง จากนั้นอีกครั้งใครจะอวดเกี่ยวกับตนเองเพื่อไม่ให้เกิดข้อผิดพลาดในโค้ด แต่คำตอบนี้มีสิ่งสำคัญหนึ่งที่ดีกว่าคำตอบอื่น ๆ ทั้งหมด: มันพยายามวัดผลที่เป็นปัญหา และคำตอบนี้ยังแบ่งปันรหัสสำหรับทุกคนที่คุณตรวจสอบและ [มวล -] downvote (ฉันสงสัยว่าทำไมจึงจำเป็นต้องลดจำนวนลง) ตรงกันข้ามกับคำตอบอื่น ๆ สมควรได้รับความเคารพในความคิดของฉัน ... นอกจากนี้โปรดทราบว่าผู้ใช้เป็นมือใหม่ดังนั้นจึงไม่ได้รับการต้อนรับที่ดีนัก การแก้ไขง่ายๆและรหัสนี้มีประโยชน์สำหรับเราทุกคน แก้ไข + แชร์เวอร์ชันคงที่ถ้าคุณกล้า
Roland Pihlakas

ฉันไม่เห็นด้วยกับ @RolandPihlakas ดังที่คุณพูดว่า "ใครจะอวดเกี่ยวกับตนเองเพื่อไม่ให้เกิดข้อผิดพลาดในรหัส" คำตอบ -> ไม่ไม่มี แต่ไม่ thats จุด. ประเด็นก็คือมันเป็นข้อมูลที่ไม่ถูกต้องซึ่งทำให้เข้าใจผิดว่าโปรแกรมเมอร์รายใหม่ หลายคนอาจพลาดได้ง่ายว่านาฬิกาจับเวลาไม่ได้ถูกรีเซ็ต พวกเขาสามารถเชื่อได้ว่าข้อมูลมาตรฐานเป็นจริง มันไม่เป็นอันตรายมากกว่านี้เหรอ? คนที่กำลังมองหาคำตอบอย่างรวดเร็วอาจไม่ได้อ่านความคิดเห็นเหล่านี้ แต่เขา / เธอจะเห็นคำตอบและเขา / เธออาจเชื่อว่า
Emran Hussain
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.