¿Las clases selladas realmente ofrecen beneficios de rendimiento?

votos
118

He encontrado una gran cantidad de consejos de optimización que dicen que debe marcar sus clases como selladas para obtener beneficios de rendimiento adicionales.

Ejecuté algunas pruebas para verificar el diferencial de rendimiento y no encontré ninguna. ¿Estoy haciendo algo mal? ¿Me estoy perdiendo el caso en que las clases selladas darán mejores resultados?

¿Alguien ha realizado pruebas y ha visto una diferencia?

Ayúdame a aprender :)

Publicado el 05/08/2008 a las 13:00
fuente por usuario
En otros idiomas...                            


11 respuestas

votos
133

La respuesta es no, las clases selladas no funcionan mejor que las no selladas.

El problema se reduce a los códigos opcionales callvs callvirtIL. Calles más rápido que callvirt, y callvirtse usa principalmente cuando no se sabe si el objeto ha sido subclasificado. Entonces la gente asume que si sellas una clase, todos los códigos de operación cambiarán de calvirtsa callsy serán más rápidos.

Desafortunadamente hay callvirtotras cosas que también lo hacen útil, como buscar referencias nulas. Esto significa que incluso si una clase está sellada, la referencia puede ser nula y, por lo tanto, callvirtse necesita una. Puede evitar esto (sin necesidad de sellar la clase), pero se vuelve un poco inútil.

Las estructuras se usan callporque no se pueden subclasificar y nunca son nulas.

Vea esta pregunta para más información:

Llamar y llamar

Respondida el 24/12/2008 a las 03:09
fuente por usuario

votos
46

JITter algunas veces usará llamadas no virtuales a métodos en clases selladas ya que no hay forma de que puedan extenderse más.

Existen reglas complejas con respecto al tipo de llamada, virtual / no virtual, y no las conozco todas, así que no puedo describirlas, pero si busca clases selladas y métodos virtuales, puede encontrar algunos artículos sobre el tema.

Tenga en cuenta que cualquier tipo de beneficio de rendimiento que obtenga de este nivel de optimización debe considerarse de último recurso, siempre optimice en el nivel algorítmico antes de optimizar en el nivel de código.

Aquí hay un enlace que menciona esto: Rambling en la palabra clave sellada

Respondida el 05/08/2008 a las 13:32
fuente por usuario

votos
20

Que yo sepa, no hay ninguna garantía de mejora en el rendimiento. Pero hay una oportunidad para disminuir la pérdida de rendimiento bajo alguna condición específica con el método de sellado. (clase sellado hace que todos los métodos que ser sellados.)

Pero depende de la implementación del compilador y entorno de ejecución.


detalles

Muchas de las CPU modernas utilizan la estructura de tuberías de largo para aumentar el rendimiento. Debido a que la CPU es increíblemente más rápido que la memoria, la CPU tiene que precargar código de la memoria para acelerar la tubería. Si el código no está listo en el momento adecuado, las tuberías serán de inactividad.

Hay un gran obstáculo llamado envío dinámico que interrumpe esta optimización 'obtención previa'. Se puede entender esto como una bifurcación condicional.

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

CPU no puede captación previa siguiente código a ejecutar en este caso debido a la posición siguiente código es desconocido hasta que se resuelva la situación. Así que esto hace peligro provoca inactivo tubería. Y la reducción del rendimiento de combustión en inactividad es muy importante en regular.

Algo similar sucede en el caso de imperiosa método. Compilador puede determinar adecuada primordial método para llamada al método actual, pero a veces es imposible. En este caso, el método adecuado se puede determinar solamente en tiempo de ejecución. Este es también un caso de distribución dinámica, y, una razón principal de los lenguajes de tipo dinámico son generalmente más lento que los lenguajes de tipo estático.

Algunos CPU (incluyendo chips x86 de Intel reciente) utiliza la técnica denominada ejecución especulativa de utilizar tuberías, incluso en la situación. Sólo uno de precargar ruta de ejecución. Pero tasa de aciertos de esta técnica no es tan alto. La especulación y el fracaso provoca la parada de tuberías que también hace gran penalización en el rendimiento. (esto es completamente por la implementación de la CPU. alguna CPU móvil se conoce como no lo hace este tipo de optimización para ahorrar energía)

Básicamente, C # es un lenguaje compilado estáticamente. Pero no siempre. No sé condición exacta y esto es totalmente de la implementación del compilador. Algunos compiladores pueden eliminar posibilidad de envío dinámico mediante la prevención de método sobrescrito si el método está marcado como sealed. Compiladores estúpida no puede. Esta es la ventaja de rendimiento de la sealed.


Esta respuesta ( ¿Por qué es más rápido para procesar una matriz ordenada de una matriz sin ordenar? ) Está describiendo la predicción de saltos mucho mejor.

Respondida el 03/02/2011 a las 16:22
fuente por usuario

votos
18

Actualización: A partir de .NET Core 2.0 y .NET de escritorio 4.7.1, el CLR ahora es compatible con devirtualization. Se puede tomar métodos en clases cerradas y reemplazar las llamadas virtuales con llamadas en directo - y también puede hacer esto por clases no selladas si puede averiguar que es seguro hacerlo.

En tal caso (una clase sellada que el CLR no pudo detectarlo de otra manera como seguro devirtualise), una clase sellada en realidad debería ofrecer algún tipo de beneficio en el rendimiento.

Dicho esto, no me creo que sería vale la pena preocuparse a menos que ya había perfilado el código y determinó que estaba en un camino particularmente caliente que se llama millones de veces, o algo por el estilo:

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 decompilados usando Reflector para ver lo que se emite código 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");
}

En todos los casos, el compilador de C # (Visual Studio 2010 en configuración Release acumulación) emite idéntica MSIL, 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 la gente sellados ofrece ventajas de rendimiento es que el compilador sabe la clase no está anulado, y por lo tanto se puede utilizar callen lugar de callvirt, ya que no tiene que comprobar si hay paquetes virtuales, etc. Como ha demostrado anteriormente, esto no es cierto.

Mi siguiente pensamiento fue que a pesar de que el MSIL es idéntica, tal vez las golosinas compilador JIT sellados clases de manera diferente?

Me encontré con una versión de lanzamiento en el depurador de Visual Studio y ver la salida x 86 decompilados. En ambos casos, el código x86 era idéntica, con la excepción de nombres de clase y las direcciones de memoria de función (que por supuesto debe ser diferente). 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 se ejecuta en el depurador hace que se realice la optimización menos agresivo?

entonces me encontré con un comunicado independiente a construir fuera ejecutable de cualquier entorno de depuración, y se utiliza WinDBG + SOS para romper después había completado el programa, y ​​ver el Desensamblaje del código compilado JIT x86.

Como se puede ver en el código siguiente, cuando se ejecuta fuera del depurador el compilador JIT es más agresivo, y se ha inline el WriteItmétodo directamente a la persona que llama. Lo crucial es sin embargo que era idéntico al llamar a un sellado contra la clase no sellada. No hay diferencia alguna entre una clase sellada o no selladas.

Aquí es cuando se llama a una clase normal de:

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 cerrada:

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 llamar a los métodos de sellado frente a las clases no selladas ... Creo que estoy feliz ahora :-)

Respondida el 20/02/2012 a las 21:45
fuente por usuario

votos
4

Marcar una clase no sealeddebería tener ningún impacto en el rendimiento.

Hay casos en los que cscpodría tener que emitir un callvirtcódigo de operación en lugar de un callcódigo de operación. Sin embargo, parece que esos casos son raros.

Y me parece que el JIT debería ser capaz de emitir la misma llamada de función no virtual para la callvirtque lo haría call, si sabe que la clase no tiene ninguna subclase (todavía). Si solo existe una implementación del método, no tiene sentido cargar su dirección desde un vtable; solo llame directamente a la implementación. Para el caso, el JIT puede incluso alinear la función.

Es un poco arriesgado por parte del JIT, porque si una subclase se carga más tarde, el JIT deberá descartar ese código máquina y compilar el código nuevamente, emitiendo una llamada virtual real. Creo que esto no sucede a menudo en la práctica.

(Y sí, los diseñadores de VM realmente persiguen agresivamente estas pequeñas ganancias de rendimiento).

Respondida el 20/11/2009 a las 10:53
fuente por usuario

votos
3

Considero "sellado" clases el caso normal y siempre tengo una razón para omitir la palabra clave "sellado".

Las razones más importantes para mí son:

a) Mejor compilar controles de tiempo (fundición a las interfaces implementadas no se detectará en tiempo de compilación, no sólo en tiempo de ejecución)

y, por la parte superior:

b) Abuso de mis clases no es posible de esa manera

Deseo Microsoft habría hecho "sellado" de la norma, no "sellar".

Respondida el 03/08/2010 a las 15:27
fuente por usuario

votos
3

<off-topic-rant>

Yo detesto clases cerradas. Incluso si los beneficios de rendimiento son asombrosos (lo cual dudo), destruyen el modelo orientado a objetos al evitar la reutilización a través de la herencia. Por ejemplo, la clase Thread está sellada. Si bien puedo ver que uno podría querer que los hilos sean lo más eficientes posible, también puedo imaginar escenarios en los que poder subprocesar Thread tendría grandes beneficios. Los autores de la clase, si debe sellar sus clases por razones de "rendimiento", proporcione una interfaz como mínimo para que no tengamos que ajustar y reemplazar en todas partes que necesitemos una característica que haya olvidado.

Ejemplo: SafeThread tuvo que envolver la clase Thread porque Thread está sellado y no hay una interfaz IThread; SafeThread atrapa automáticamente las excepciones no controladas en los hilos, algo que falta por completo en la clase Thread. [y no, los eventos de excepción no controlados no recogen excepciones no controladas en subprocesos secundarios].

</ off-topic-rant>

Respondida el 14/10/2008 a las 21:04
fuente por usuario

votos
3

Las clases selladas deberían proporcionar una mejora en el rendimiento. Como no se puede derivar una clase sellada, cualquier miembro virtual puede convertirse en miembro no virtual.

Por supuesto, estamos hablando de ganancias realmente pequeñas. No marcaría una clase como sellada solo para obtener una mejora en el rendimiento a menos que el perfil revele que es un problema.

Respondida el 05/08/2008 a las 13:37
fuente por usuario

votos
2

clases sellados serán al menos un poquito más rápido, pero a veces pueden ser waayyy más rápido ... si el Optimizador de JIT puede inline llamadas que habría sido de otro modo llamadas virtuales. Así, donde hay tantas veces denominado métodos que son lo suficientemente pequeños para ser inline, considerar definitivamente sellado de la clase.

Sin embargo, la mejor razón para sellar una clase es decir "No diseñé esto a ser heredada de, por lo que no voy a dejar que se queme por si se asume que fue diseñado para ser así y no voy para quemar a mí mismo por quedar encerrados en una implementación porque dejo que de ella se derivan ".

Sé que algunos aquí he dicho que odio clases cerradas porque quieren la oportunidad de derivar de cualquier cosa ... pero eso no es a menudo la opción más fácil de mantener ... porque la exposición de una clase a la derivación que las cerraduras en un montón más de no exponer todo ese. Su similar a decir "Aborrezco clases que tienen los miembros privados ... A menudo no puedo hacer que la clase hacer lo que quiera porque no tengo acceso." La encapsulación es importante ... sellado es una forma de encapsulación.

Respondida el 01/10/2011 a las 11:37
fuente por usuario

votos
2

@Vaibhav, ¿qué tipo de pruebas ejecutó para medir el rendimiento?

Supongo que uno debería usar Rotor y profundizar en CLI y comprender cómo una clase sellada mejoraría el rendimiento.

SSCLI (Rotor)
SSCLI: Infraestructura de lenguaje común de fuente compartida

Common Language Infrastructure (CLI) es el estándar ECMA que describe el núcleo del .NET Framework. La CLI de fuente compartida (SSCLI), también conocida como Rotor, es un archivo comprimido del código fuente para una implementación operativa de la CLI ECMA y la especificación del lenguaje ECMA C #, tecnologías en el corazón de la arquitectura .NET de Microsoft.

Respondida el 05/08/2008 a las 13:40
fuente por usuario

votos
-9

Ejecute este código y verá que las clases selladas son 2 veces más rápidas:

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";
    }
}

salida: Clase sellada: 00: 00: 00.1897568 Clase no sellada: 00: 00: 00.3826678

Respondida el 26/11/2009 a las 18:50
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more