171 votes

Les classes sealed offrent-ils vraiment des avantages de performances ?

Je suis tombé sur un grand nombre de conseils d’optimisation qui disent que vous devez marquer vos classes comme sealed pour obtenir des avantages de performance supplémentaire.

J’ai couru quelques tests pour vérifier la performance prime et rien trouvé. J’ai fais quelque chose de mal ? Est-ce que je suis absent le cas où des classes sealed donnera de meilleurs résultats ?

Quelqu'un at-il des tests et vu une différence ?

M’aider à apprendre  :)

174voto

Cameron MacFarland Points 27240

La réponse est non, scellé classes ne réussissent mieux que les non-scellées.

La question revient à l' call vs callvirt IL op codes. Call plus rapide que de l' callvirt, et callvirt est principalement utilisé lorsque vous ne savez pas si l'objet a été sous-classé. Donc, les gens pensent que si vous joint une classe, tous les op les codes de changer de calvirts de calls et sera plus rapide.

Malheureusement, callvirt fait d'autres choses qui font qu'il est utile aussi, comme la vérification de références nulles. Cela signifie que même si une classe est scellé, la référence peut encore être null et donc un callvirt est nécessaire. Vous pouvez contourner ce problème (sans avoir besoin de sceller la classe), mais il devient un peu inutile.

Les structures d'utiliser call parce qu'ils ne peuvent pas être sous-classé et ne sont jamais nulles.

Voir cette question pour plus d'informations:

Appel et callvirt

65voto

Lasse V. Karlsen Points 148037

La Gigue utilisent parfois des non-virtuel les appels de méthodes dans des classes car il n'y a aucun moyen qu'ils peuvent être étendus.

Il existe des règles concernant l'appel de type virtuel/nonvirtual, et je ne les connais pas tous donc je ne peux pas vraiment décrire pour vous, mais si vous faites une recherche google pour les scellés de classes et de méthodes virtuelles que vous pouvez trouver des articles sur le sujet.

Notez que n'importe quel type de rendement bénéfice que vous obtenir à partir de ce niveau d'optimisation doit être considérée comme le dernier recours, toujours optimiser sur l'algorithmique niveau avant d'optimiser le niveau du code.

Voici un lien mentionnant ceci: Randonnée sur le mot clé sealed

37voto

Orion Edwards Points 54939

J'ai fait le programme de test suivant, puis décompilé l'aide d'un Réflecteur pour voir ce que le code MSIL a été émise.

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

Dans tous les cas, le compilateur C# (Visual studio 2010), dans la Version de configuration de la compilation) émet identiques MSIL, qui est comme suit:

    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 

Souvent cité raison que les gens disent scellé offre des avantages de performance, c'est que le compilateur sait que la classe n'est pas surchargé, et peut donc utiliser call au lieu de callvirt comme il n'a pas à vérifier les virtuals, etc. Comme l'a démontré ci-dessus, ce n'est pas vrai.

Ma prochaine pensée était que, même si le MSIL est identique, peut-être que le compilateur JIT traite scellé classes différemment?

J'ai couru un communiqué de construire sous le débogueur de visual studio et vu le décompilé x86 de sortie. Dans les deux cas, le x86 code est identique, à l'exception des noms de classe et la fonction des adresses de mémoire (qui bien sûr doit être différent). Ici, il 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 

J'ai alors pensé que peut-être en cours d'exécution sous le débogueur entraîne à effectuer moins agressives d'optimisation?

J'ai ensuite couru une version monoposte générer un exécutable, en dehors de toute environnements de débogage, et utilisé WinDBG + SOS pour casser une fois que le programme avait terminé, et vue la dissasembly de l'équipe compilé x86 code.

Comme vous pouvez le voir dans le code ci-dessous, lors de l'exécution à l'extérieur du débogueur le compilateur JIT est plus agressif, et il a incorporé l' WriteIt méthode de la droite en l'appelant. L'essentiel cependant est qu'il était identique lors de l'appel d'un scellé vs non-classe scellée. Il n'y a aucune différence entre un scellé ou nonsealed classe.

Ici, c'est lors de l'appel d'une classe normale:

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 une classe scellée:

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

Pour moi, cela fournit une preuve solide qu'il ne peut pas être n'importe quelle amélioration de la performance entre les appels de méthodes sur des scellés vs non scellées à des classes... je pense que je suis heureux maintenant :-)

25voto

Eonil Points 19404

Que je sache, il n'existe aucune garantie de performances. Mais il y a une chance de diminuer les performances de pénalités en vertu d'une condition spécifique avec les scellés de la méthode. (scellé classe rend toutes les méthodes pour être scellé.)

Mais c'est le compilateur de la mise en œuvre et de l'environnement d'exécution.


Détails

Beaucoup de Processeurs modernes utilisent un long pipeline de la structure pour augmenter les performances. Parce que le CPU est incroyablement plus rapide que la mémoire, le PROCESSEUR est à la prélecture de code à partir de la mémoire pour accélérer le pipeline. Si le code n'est pas prêt au bon moment, les pipelines seront inactives.

Il y a un gros obstacle appelé répartition dynamique qui perturbe cette "pré-chargement' optimisation. Vous pouvez comprendre cela comme une branchements conditionnels.

// 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();

PROCESSEUR ne peut pas prefetch suivant le code à exécuter dans ce cas, car le code suivant dans la position est inconnue jusqu'à ce que la condition est résolu. Cela fait donc de danger provoque pipeline d'inactivité. Et les performances en idle est énorme dans l'ordinaire.

Une chose semblable se produire dans le cas de la redéfinition de méthode. Le compilateur peut déterminer la méthode appropriée primordial pour le courant de l'appel de méthode, mais il est parfois impossible. Dans ce cas, la bonne méthode ne peut être déterminée qu'au moment de l'exécution. C'est aussi une affaire de répartition dynamique, et une des raisons principales de dynamiquement typés langues sont généralement plus lents que statiquement typé langues.

L'UC (y compris les récents d'Intel x86 jetons) utilise la technique dite spéculative de l'exécution à utiliser le pipeline de même sur la situation. Juste prefetch l'un de chemin d'exécution. Mais le taux de succès de cette technique n'est pas si élevé. Et spéculation sur les causes d'un échec pipeline de décrochage, ce qui fait aussi énorme sur les performances. (c'est complètement par la mise en œuvre CPU. certains de PROCESSEUR mobile est connu comme n'a pas ce genre d'optimisation pour économiser de l'énergie)

Fondamentalement, le C# est un langage compilé statiquement. Mais pas toujours. Je ne sais pas état exact et c'est le compilateur de mise en œuvre. Certains compilateurs peuvent éliminer la possibilité d'une répartition dynamique par la prévention de la redéfinition de méthode si la méthode est marqué comme sealed. Stupide les compilateurs peuvent pas. C'est l'avantage de performance de l' sealed.


Cette réponse (Pourquoi le traitement d'un tableau trié de plus qu'un tableau non trié?) est de décrire la direction de la prévision beaucoup mieux.

7voto

Steven A. Lowe Points 40596

<hors-sujet-coup de gueule>

Je déteste classes scellées. Même si les avantages de performances sont étonnantes (ce dont je doute), ils détruisent le modèle orienté-objet en empêchant la réutilisation via l'héritage. Par exemple, la classe Thread est scellé. Bien que je ne peux voir qu'on peut avoir envie de threads pour être aussi efficace que possible, je peux aussi imaginer des scénarios où la sous-classe de Thread aurait de grands avantages. Classe les auteurs, si vous devez sceller vos classes pour les "performances" des raisons, veuillez fournir une interface à tout le moins, de sorte que nous n'avons pas d'envelopper et de remplacer partout que nous avons besoin d'une fonctionnalité que vous avez oublié.

Exemple: SafeThread avait pour envelopper la classe Thread parce que le Thread est scellé et il n'y a pas de IThread de l'interface; SafeThread automatiquement les pièges les exceptions non gérées sur les threads, quelque chose de complètement absents de la classe Thread. [et non, l'exception non gérée événements à ne pas ramasser les exceptions non gérées dans des threads secondaires].

</off-topic-coup de gueule>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X