Actualización: a partir de .NET Core 2.0 y .NET Desktop 4.7.1, el CLR ahora admite la desvirtualización. Puede tomar métodos en clases selladas y reemplazar llamadas virtuales con llamadas directas, y también puede hacerlo para clases no selladas si puede descubrir que es seguro hacerlo.
En tal caso (una clase sellada que el CLR no podría detectar como seguro para desvirtualizar), una clase sellada debería ofrecer algún tipo de beneficio de rendimiento.
Dicho esto, no creo que valga la pena preocuparse a menos que ya haya perfilado el código y haya determinado que estaba en una ruta particularmente caliente que se llamaba millones de veces, o algo así:
https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/
Respuesta original
Hice el siguiente programa de prueba y luego lo descomprimí usando Reflector para ver qué código MSIL se emitió.
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");
}
En todos los casos, el compilador de C # (Visual studio 2010 en la configuración de la versión de lanzamiento) emite MSIL idéntico, que es el siguiente:
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
La razón citada a menudo por la cual las personas dicen que el sellado proporciona beneficios de rendimiento es que el compilador sabe que la clase no se anula y, por lo tanto, puede usarla en call
lugar de callvirt
no tener que buscar virtuales, etc. Como se demostró anteriormente, esto no es cierto.
Mi siguiente pensamiento fue que, aunque el MSIL es idéntico, ¿quizás el compilador JIT trata las clases selladas de manera diferente?
Ejecuté una versión de lanzamiento bajo el depurador visual studio y vi la salida x86 descompilada. En ambos casos, el código x86 era idéntico, con la excepción de los nombres de clase y las direcciones de memoria de funciones (que, por supuesto, deben ser diferentes). Aquí está
// 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
Entonces pensé que tal vez correr bajo el depurador hace que realice una optimización menos agresiva.
Luego ejecuté un ejecutable de compilación de versión independiente fuera de cualquier entorno de depuración, y usé WinDBG + SOS para entrar después de que el programa se había completado, y ver el desensamblaje del código x86 compilado JIT.
Como puede ver en el siguiente código, cuando se ejecuta fuera del depurador, el compilador JIT es más agresivo y ha incorporado el WriteIt
método directamente a la persona que llama. Sin embargo, lo crucial es que era idéntico al llamar a una clase sellada frente a una no sellada. No hay diferencia alguna entre una clase sellada o no sellada.
Aquí es cuando se llama a una clase normal:
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 una clase sellada:
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
Para mí, esto proporciona una prueba sólida de que no puede haber ninguna mejora en el rendimiento entre los métodos de llamada en clases selladas frente a no selladas ... Creo que estoy contento ahora :-)