추가 성능 이점을 얻으려면 클래스를 봉인으로 표시해야한다고 말하는 많은 최적화 팁을 보았습니다.
성능 차이를 확인하기 위해 몇 가지 테스트를 실행했지만 아무것도 발견하지 못했습니다. 내가 뭔가 잘못하고 있습니까? 봉인 클래스가 더 나은 결과를 제공하는 사례를 놓치고 있습니까?
누구든지 테스트를 실행하고 차이를 보았습니까?
배우도록 도와주세요 :)
추가 성능 이점을 얻으려면 클래스를 봉인으로 표시해야한다고 말하는 많은 최적화 팁을 보았습니다.
성능 차이를 확인하기 위해 몇 가지 테스트를 실행했지만 아무것도 발견하지 못했습니다. 내가 뭔가 잘못하고 있습니까? 봉인 클래스가 더 나은 결과를 제공하는 사례를 놓치고 있습니까?
누구든지 테스트를 실행하고 차이를 보았습니까?
배우도록 도와주세요 :)
답변:
JITter는 더 이상 확장 할 수있는 방법이 없기 때문에 봉인 된 클래스의 메소드에 대한 비가 상 호출을 사용하기도합니다.
전화 유형, 가상 / 비가 상 전화에 관한 복잡한 규칙이 있으며, 나는 그것들을 모두 알지 못하므로 실제로 당신을 위해 그것을 설명 할 수는 없지만 봉인 클래스와 가상 메소드를 구글로 검색하면 주제에 대한 기사를 찾을 수 있습니다.
이 수준의 최적화를 통해 얻을 수있는 모든 종류의 성능 이점은 최후의 수단으로 간주해야하며 코드 수준에서 최적화하기 전에 항상 알고리즘 수준에서 최적화해야합니다.
다음은이를 언급하는 링크 입니다. 봉인 된 키워드에 대한 램 블링
답은 아닙니다. 봉인 된 수업은 봉인되지 않은 수업보다 더 잘 수행되지 않습니다.
이 문제는 call
vs callvirt
op op 코드와 관련이 있습니다. Call
이다보다 빨리 callvirt
, 그리고 callvirt
객체가 서브 클래 싱 된 경우 당신이 모르는 경우에 주로 사용된다. 따라서 사람들은 클래스를 봉인하면 모든 op 코드가에서 calvirts
로 바뀌고 calls
더 빠를 것이라고 가정합니다.
불행히도 callvirt
null 참조 확인과 같이 유용하게 사용되는 다른 작업을 수행합니다. 이것은 클래스가 봉인 되더라도 참조가 여전히 널이어서 a callvirt
가 필요하다는 것을 의미합니다. 클래스를 봉인 할 필요없이이 문제를 해결할 수 있지만 조금 의미가 없습니다.
Structs call
는 서브 클래 싱 할 수없고 null이 아니므로 사용 합니다.
자세한 내용은이 질문을 참조하십시오.
call
은 다음과 같습니다. 상황 new T().Method()
, struct
메소드, 비가 상 virtual
메소드 (예 :) 호출 base.Virtual()
또는 static
메소드. 다른 곳에서는를 사용합니다 callvirt
.
업데이트 : .NET Core 2.0 및 .NET Desktop 4.7.1부터 CLR은 이제 가상화를 지원합니다. 봉인 클래스의 메소드를 사용하고 가상 호출을 직접 호출로 대체 할 수 있으며, 안전한지 알아낼 수있는 경우 봉인되지 않은 클래스에 대해서도이를 수행 할 수 있습니다.
이 경우 (CLR이 달리 안전하다고 생각할 수없는 봉인 클래스) 봉인 클래스는 실제로 일종의 성능 이점을 제공해야합니다.
즉, 이미 코드를 프로파일 링하고 특히 수백만 번 호출되는 핫 경로 또는 그와 같은 것으로 결정 되지 않은 경우 걱정할 가치가 없다고 생각합니다 .
원래 답변 :
다음 테스트 프로그램을 만든 다음 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)는 동일한 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
그런 다음 디버거에서 실행하면 덜 공격적인 최적화를 수행한다고 생각 했습니까?
그런 다음 디버깅 환경 외부에서 독립 실행 형 릴리스 빌드 실행 파일을 실행하고 프로그램이 완료된 후 WinDBG + SOS를 사용하여 JIT 컴파일 된 x86 코드의 해산을 확인했습니다.
아래 코드에서 볼 수 있듯이 디버거 외부에서 실행할 때 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
봉인 클래스 대 :
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
나에게 이것은 밀봉 클래스와 비밀 봉 클래스에서 메소드를 호출하는 사이에 성능 향상이 불가능 하다는 확실한 증거를 제공합니다 ... 지금은 행복하다고 생각합니다 :-)
callvirt
이러한 방법이 가상적이지 않은 이유는 무엇입니까?
callvirt
은 봉인 된 메소드에 사용되었다는 것을 읽었다 . 메서드 호출을 호출하기 전에 객체를 null 검사해야하기 때문에 인수를 고려하면 사용하십시오 callvirt
. 를 제거 callvirt
하고 바로 점프 하려면 ((string)null).methodCall()
C ++처럼 허용하도록 C #을 수정해야 하거나 객체가 null이 아니었다는 것을 정적으로 증명해야합니다 (할 수는 있지만 귀찮게하지는 않았습니다)
아시다시피 성능 이점을 보장 할 수는 없습니다. 그러나 밀폐 된 방법을 사용하면 특정 조건에서 성능 저하를 줄일 수 있습니다. (밀봉 클래스는 모든 메소드를 봉인합니다.)
그러나 컴파일러 구현 및 실행 환경에 달려 있습니다.
현대의 많은 CPU는 긴 파이프 라인 구조를 사용하여 성능을 향상시킵니다. CPU는 메모리보다 엄청나게 빠르기 때문에 CPU는 파이프 라인을 가속화하기 위해 메모리에서 코드를 프리 페치해야합니다. 코드가 적절한 시간에 준비되지 않으면 파이프 라인이 유휴 상태가됩니다.
이 '프리 페치'최적화를 방해하는 동적 디스패치 라는 큰 장애물 이 있습니다. 이것을 조건부 분기로 이해할 수 있습니다.
// 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 칩 포함)는 상황에 따라 파이프 라인을 활용하기 위해 추론 적 실행 이라는 기술 을 사용합니다. 실행 경로 중 하나를 프리 페치하십시오. 그러나이 기술의 적중률은 그리 높지 않습니다. 그리고 투기 실패로 인해 파이프 라인이 중단되어 성능이 크게 저하됩니다. (이것은 CPU 구현에 의한 것입니다. 일부 모바일 CPU는 에너지 절약을 위해 이러한 종류의 최적화가 아닌 것으로 알려져 있습니다)
기본적으로 C #은 정적으로 컴파일 된 언어입니다. 그러나 항상 그런 것은 아닙니다. 나는 정확한 조건을 모른다. 그리고 이것은 전적으로 컴파일러 구현에 달려있다. 일부 컴파일러는 메서드가로 표시된 경우 메서드 재정의를 방지하여 동적 디스패치 가능성을 제거 할 수 있습니다 sealed
. 어리석은 컴파일러는 그렇지 않을 수 있습니다. 이것이의 성능 이점입니다 sealed
.
이 답변 ( 정렬되지 않은 배열보다 정렬 된 배열을 처리하는 것이 왜 더 빠릅니까? )은 분기 예측을 훨씬 잘 설명합니다.
<주제 이외의 범위>
나는 봉인 된 수업을 싫어 합니다. 성능상의 이점이 놀랍더라도 (물론 의심 스럽지만) 상속을 통한 재사용을 방지하여 객체 지향 모델 을 파괴 합니다. 예를 들어 Thread 클래스는 봉인되어 있습니다. 스레드가 가능한 효율적 이길 원한다는 것을 알 수 있지만 스레드를 서브 클래 싱 할 수 있으면 큰 이점이있는 시나리오도 상상할 수 있습니다. 수업 저자 는 "성능"이유로 수업을 봉인 해야하는 경우 최소한 인터페이스를 제공하여 잊어 버린 기능이 필요한 모든 곳에서 랩핑 및 교체 할 필요가 없도록하십시오.
예 : Thread가 봉인되고 IThread 인터페이스가 없기 때문에 SafeThread 는 Thread 클래스를 래핑해야했습니다. SafeThread는 스레드에서 처리되지 않은 예외를 자동으로 트랩합니다. 이는 스레드 클래스에서 완전히 누락 된 것입니다. 또한 처리되지 않은 예외 이벤트는 보조 스레드에서 처리되지 않은 예외를 선택 하지 않습니다 .
</ off-topic-rant>
클래스를 표시하면 sealed
성능에 영향을 미치지 않아야합니다.
opcode 대신 opcode csc
를 방출해야하는 경우가 있습니다 . 그러나 그러한 경우는 드물다.callvirt
call
그리고 클래스에 하위 클래스가 아직 없다는 것을 알고 있다면 JIT는 동일한 비 가상 함수 호출을 수행 callvirt
할 수 call
있어야합니다. 메소드의 구현이 하나만 존재하는 경우 vtable에서 주소를로드 할 필요가 없습니다. 하나의 구현을 직접 호출하면됩니다. JIT는 기능을 인라인 할 수도 있습니다.
서브 클래스 가 나중에로드되면 JIT는 해당 기계 코드를 버리고 코드를 다시 컴파일하여 실제 가상 호출을 생성해야하기 때문에 JIT 부분에서 약간의 도박 입니다. 내 생각에 이것은 실제로 자주 발생하지 않습니다.
(그렇습니다. VM 디자이너는 이러한 작은 성능의 승리를 적극적으로 추구합니다.)
봉인 클래스 는 성능 향상을 제공 해야 합니다. 봉인 클래스는 파생 될 수 없으므로 모든 가상 구성원을 비가 상 구성원으로 전환 할 수 있습니다.
물론, 우리는 정말 작은 이익을 이야기하고 있습니다. 프로파일 링에서 문제를 나타내지 않는 한 성능 향상을 위해 클래스를 봉인 된 것으로 표시하지 않습니다.
call
대신 방출 할 수 있기 때문에 봉인 클래스가 실제로 더 좋습니다 callvirt
... 다른 많은 이유로 인해 null이 아닌 참조 유형을 좋아합니다 ... 한숨 :-(
나는 "봉인 된"클래스를 일반적인 경우로 생각하고 항상 "봉인 된"키워드를 생략 할 이유가 있습니다.
가장 중요한 이유는 다음과 같습니다.
a) 더 나은 컴파일 시간 검사 (구현되지 않은 인터페이스에 대한 캐스팅은 런타임뿐만 아니라 컴파일 시간에 감지됩니다)
그리고 가장 큰 이유 :
b) 내 수업의 남용은 그런 식으로 불가능합니다
마이크로 소프트가 "밀봉되지 않은"표준이 아닌 "밀봉 된"표준을 만들었기를 바란다.
@Vaibhav, 성능을 측정하기 위해 어떤 종류의 테스트를 실행 했습니까?
Rotor 를 사용 하고 CLI를 뚫고 봉인 클래스가 성능을 향상시키는 방법을 이해해야한다고 생각합니다.
SSCLI (로터)
SSCLI : 공유 소스 공용 언어 인프라CLI (공용 언어 인프라)는 .NET Framework의 핵심을 설명하는 ECMA 표준입니다. Rotor라고도하는 SSCLI (공유 소스 CLI)는 Microsoft .NET 아키텍처의 핵심 인 ECMA CLI 및 ECMA C # 언어 사양의 실제 구현에 대한 소스 코드의 압축 된 아카이브입니다.
JIT Optimizer가 가상 호출이었던 호출을 인라인 할 수있는 경우 봉인 된 클래스는 조금 더 빠를 수 있지만 때로는 waayyy가 더 빠를 수 있습니다. 따라서 인라인하기에 충분히 작은 메서드라고 불리는 클래스가있는 경우 클래스를 봉인하는 것이 좋습니다.
그러나 수업을 봉인하는 가장 좋은 이유는 "이것을 상속 받도록 설계하지 않았기 때문에 그렇게 설계된 것으로 가정하여 화상을 입지 않도록 할 것입니다. 구현에서 멈춰서 자신을 태워 버릴 것입니다. "
나는 여기에서 어떤 사람들이 무언가에서 파생 될 수있는 기회를 원하기 때문에 봉인 된 수업을 싫어한다고 말했음을 알고 있습니다 ...하지만 가장 유지하기 쉬운 선택은 아닙니다 ... 그. "개인 멤버가있는 수업을 싫어합니다. 액세스 권한이 없기 때문에 원하는 수업을 할 수없는 경우가 많습니다."라고 말하는 것과 비슷합니다. 캡슐화가 중요합니다 ... 밀봉은 캡슐화의 한 형태입니다.
callvirt
씰링 된 클래스의 메서드에 대해 (가상 메서드 호출)을 사용합니다.이 메서드는 여전히 null 개체 검사를 수행해야하기 때문입니다. 인라인과 관련하여 CLR JIT는 봉인 된 클래스와 봉인되지 않은 클래스 모두에 대한 인라인 가상 메소드 호출을 수행 할 수 있습니다 (그렇습니다). 성능은 신화입니다.
그것들을 실제로 보려면 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에서 다음 명령의 속도는 다음과 같습니다.
링크 : https://gmplib.org/~tege/x86-timing.pdf
즉, 이상적으로 는 봉인 된 메소드를 호출하는 데 필요한 시간은 2주기이고 파생 또는 기본 클래스 메소드를 호출하는 데 필요한 시간은 3주기입니다.
컴파일러 최적화는 봉인 된 클래스와 봉인되지 않은 클래스의 성능을 너무 낮게 설정하여 프로세서 원에 대해 이야기하고 있으며, 이러한 이유로 대부분의 응용 프로그램과 관련이 없습니다.
이 코드를 실행하면 봉인 클래스가 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