봉인 된 수업은 실제로 성과 이점을 제공합니까?
추가 성능 이점을 얻으려면 클래스를 봉인으로 표시해야한다고 말하는 많은 최적화 팁을 보았습니다.
성능 차이를 확인하기 위해 몇 가지 테스트를 실행했지만 아무것도 발견하지 못했습니다. 내가 뭔가 잘못하고 있습니까? 봉인 클래스가 더 나은 결과를 제공하는 사례를 놓치고 있습니까?
누구든지 테스트를 실행하고 차이를 보았습니까?
배우도록 도와주세요 :)
JITter는 더 이상 확장 할 수있는 방법이 없기 때문에 봉인 된 클래스의 메소드에 대한 비가 상 호출을 사용하기도합니다.
전화 유형, 가상 / 비가 상 전화에 관한 복잡한 규칙이 있으며, 나는 그것들을 모두 알지 못하므로 실제로 당신을 위해 그것을 설명 할 수는 없지만 봉인 클래스와 가상 메소드를 구글로 검색하면 주제에 대한 기사를 찾을 수 있습니다.
이 수준의 최적화를 통해 얻을 수있는 모든 종류의 성능 이점은 최후의 수단으로 간주해야하며 코드 수준에서 최적화하기 전에 항상 알고리즘 수준에서 최적화해야합니다.
다음은이를 언급하는 링크 입니다. 봉인 된 키워드에 대한 비난
답은 아닙니다. 봉인 된 수업은 봉인되지 않은 수업보다 더 잘 수행되지 않습니다.
이 문제는 call
vs callvirt
op op 코드와 관련이 있습니다. Call
이다보다 빨리 callvirt
, 그리고 callvirt
객체가 서브 클래 싱 된 경우 당신이 모르는 경우에 주로 사용된다. 따라서 사람들은 클래스를 봉인하면 모든 op 코드가에서 calvirts
로 바뀌고 calls
더 빠를 것이라고 가정합니다.
불행히도 callvirt
null 참조 확인과 같이 유용하게 사용할 수있는 다른 작업을 수행합니다. 이것은 클래스가 봉인 되더라도 참조가 여전히 널이어서 a callvirt
가 필요하다는 것을 의미합니다. 이 문제를 해결할 수는 있지만 (클래스를 봉인 할 필요없이) 조금 의미가 없습니다.
Structs call
는 서브 클래 싱 할 수없고 null이 아니므로 사용 합니다.
자세한 내용은이 질문을 참조하십시오.
업데이트 : .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
사람들이 Sealed가 성능상의 이점을 제공한다고 종종 언급하는 이유는 컴파일러가 클래스가 재정의되지 않는다는 것을 알고 있기 때문에 가상 등을 확인할 필요가 없기 때문에 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
나에게 이것은 밀봉 클래스와 비밀 봉 클래스에서 메소드를 호출하는 사이에 성능 향상이 불가능 하다는 확실한 증거를 제공합니다 ... 지금은 행복하다고 생각합니다 :-)
As I know, there is no guarantee of performance benefit. But there is a chance to decrease performance penalty under some specific condition with sealed method. (sealed class makes all methods to be sealed.)
But it's up to compiler implementation and execution environment.
Details
Many of modern CPUs use long pipeline structure to increase performance. Because CPU is incredibly faster than memory, CPU has to prefetch code from memory to accelerate pipeline. If the code is not ready at proper time, the pipelines will be idle.
There is a big obstacle called dynamic dispatch which disrupts this 'prefetching' optimization. You can understand this as just a conditional branching.
// 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 cannot prefetch next code to execute in this case because the next code position is unknown until the condition is resolved. So this makes hazard causes pipeline idle. And performance penalty by idle is huge in regular.
Similar thing happen in case of method overriding. Compiler may determine proper method overriding for current method call, but sometimes it's impossible. In this case, proper method can be determined only at runtime. This is also a case of dynamic dispatch, and, a main reason of dynamically-typed languages are generally slower than statically-typed languages.
Some CPU (including recent Intel's x86 chips) uses technique called speculative execution to utilize pipeline even on the situation. Just prefetch one of execution path. But hit rate of this technique is not so high. And speculation failure causes pipeline stall which also makes huge performance penalty. (this is completely by CPU implementation. some mobile CPU is known as does not this kind of optimization to save energy)
Basically, C# is a statically compiled language. But not always. I don't know exact condition and this is entirely up to compiler implementation. Some compilers can eliminate possibility of dynamic dispatch by preventing method overriding if the method is marked as sealed
. Stupid compilers may not. This is the performance benefit of the sealed
.
This answer (Why is it faster to process a sorted array than an unsorted array?) is describing the branch prediction a lot better.
<off-topic-rant>
I loathe sealed classes. Even if the performance benefits are astounding (which I doubt), they destroy the object-oriented model by preventing reuse via inheritance. For example, the Thread class is sealed. While I can see that one might want threads to be as efficient as possible, I can also imagine scenarios where being able to subclass Thread would have great benefits. Class authors, if you must seal your classes for "performance" reasons, please provide an interface at the very least so we don't have to wrap-and-replace everywhere that we need a feature you forgot.
Example: SafeThread had to wrap the Thread class because Thread is sealed and there is no IThread interface; SafeThread automatically traps unhandled exceptions on threads, something completely missing from the Thread class. [and no, the unhandled exception events do not pick up unhandled exceptions in secondary threads].
</off-topic-rant>
Marking a class sealed
should have no performance impact.
There are cases where csc
might have to emit a callvirt
opcode instead of a call
opcode. However, it seems those cases are rare.
And it seems to me that the JIT should be able to emit the same non-virtual function call for callvirt
that it would for call
, if it knows that the class doesn't have any subclasses (yet). If only one implementation of the method exists, there's no point loading its address from a vtable—just call the one implementation directly. For that matter, the JIT can even inline the function.
It's a bit of a gamble on the JIT's part, because if a subclass is later loaded, the JIT will have to throw away that machine code and compile the code again, emitting a real virtual call. My guess is this doesn't happen often in practice.
(And yes, VM designers really do aggressively pursue these tiny performance wins.)
Sealed classes should provide a performance improvement. Since a sealed class cannot be derived, any virtual members can be turned into non-virtual members.
Of course, we're talking really small gains. I wouldn't mark a class as sealed just to get a performance improvement unless profiling revealed it to be a problem.
I consider "sealed" classes the normal case and I ALWAYS have a reason to omit the "sealed" keyword.
The most important reasons for me are:
a) Better compile time checks (casting to interfaces not implemented will be detected at compile time, not only at runtime)
and, top reason:
b) Abuse of my classes is not possible that way
I wish Microsoft would have made "sealed" the standard, not "unsealed".
@Vaibhav, what kind of tests did you execute to measure performance?
I guess one would have to use Rotor and to drill into CLI and understand how a sealed class would improve performance.
SSCLI (Rotor)
SSCLI: Shared Source Common Language InfrastructureThe Common Language Infrastructure (CLI) is the ECMA standard that describes the core of the .NET Framework. The Shared Source CLI (SSCLI), also known as Rotor, is a compressed archive of the source code to a working implementation of the ECMA CLI and the ECMA C# language specification, technologies at the heart of Microsoft’s .NET architecture.
sealed classes will be at least a tiny bit faster, but sometimes can be waayyy faster... if the JIT Optimizer can inline calls that would have otherwise been virtual calls. So, where there's oft-called methods that are small enough to be inlined, definitely consider sealing the class.
However, the best reason to seal a class is to say "I didn't design this to be inherited from, so I'm not going to let you get burned by assuming it was designed to be so, and I'm not going to burn myself by getting locked into an implementation because I let you derive from it."
I know some here have said they hate sealed classes because they want the opportunity to derive from anything... but that is OFTEN not the most maintainable choice... because exposing a class to derivation locks you in a lot more than not exposing all that. Its similar to saying "I loathe classes that have private members... I often can't make the class do what I want because I don't have access." Encapsulation is important... sealing is one form of encapsulation.
Run this code and you'll see that sealed classes are 2 times faster:
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";
}
}
output: Sealed class : 00:00:00.1897568 NonSealed class : 00:00:00.3826678
참고URL : https://stackoverflow.com/questions/2134/do-sealed-classes-really-offer-performance-benefits
'IT' 카테고리의 다른 글
JSONArray를 반복 할 수 있습니까? (0) | 2020.06.29 |
---|---|
IntelliJ IDEA의 매개 변수 목록 안에있을 때 메소드 호출 후 세미콜론을 추가하는 방법은 무엇입니까? (0) | 2020.06.29 |
.NET에서 프로세스를 생성하고 STDOUT을 캡처하는 방법은 무엇입니까? (0) | 2020.06.29 |
정수에서 Java의 로그베이스 2를 어떻게 계산합니까? (0) | 2020.06.29 |
XML에서 속성 값 얻기 (0) | 2020.06.29 |