10 votes

Quel est un exemple concret d'explosion de code préjudiciable causée par des initialisations de champs ?

Sur CLR via C# Richter fait remarquer que l'initialisation des champs dans une déclaration de classe, comme suit

class C {
  int x = 3;
  int y = 4;

  public C() { ... }
  public C(int z) { ... }

  ...
}

entraîne l'insertion d'instructions au début de chaque constructeur qui définissent les champs avec les valeurs fournies. Ainsi, la ligne int x = 3; ci-dessus sera responsable de deux initialisations distinctes, l'une dans le constructeur sans paramètre et l'autre dans le constructeur qui prend un fichier int argument.

Richter poursuit :

Cela signifie que vous devez être conscient de l'explosion du code [...] Si vous avez plusieurs champs d'instance initialisés et beaucoup de méthodes de constructeur surchargées, vous devriez envisager de définir les champs sans l'initialisation, de créer un constructeur unique qui effectue l'initialisation commune et de faire en sorte que chaque constructeur appelle explicitement le constructeur d'initialisation commune. Cette approche permettra de réduire la taille du code généré.

J'ai du mal à imaginer un scénario dans lequel cela deviendrait un problème notable, ce qui me fait me demander si je ne manque pas quelque chose ici. Par exemple, si nous imaginons que notre classe possède dix constructeurs et une centaine de champs et qu'il faut, disons, seize octets de code machine natif pour effectuer une initialisation, nous parlons alors d'un total de 16 kB de code natif généré. Il s'agit certainement d'une quantité négligeable de mémoire sur n'importe quel ordinateur de ce siècle, non ?

J'imagine que l'utilisation de génériques pourrait multiplier ce chiffre par un petit facteur, mais l'impact sur l'ensemble du travail semble tout de même assez faible.

Question : Est-ce que je rate quelque chose ici, et si oui, quoi ?

Bien que ma question soit principalement théorique - je veux tester ma propre compréhension - elle est aussi un peu pratique, car initialiser les champs là où ils sont déclarés semble produire un code beaucoup plus lisible que d'utiliser un constructeur centralisé comme le suggère Richter.

3voto

Eric Lippert Points 300275

Quel est un exemple concret d'explosion de code préjudiciable causée par des initialisations de champs ?

Je ne connais pas d'exemple concret de ce problème.

J'ai du mal à imaginer un scénario dans lequel cela deviendrait un problème notable.

Moi aussi.

ce qui me fait me demander si je ne rate pas quelque chose ici.

Pas à ma connaissance.

C'est sûrement une quantité négligeable de mémoire sur n'importe quel ordinateur de ce siècle, non ?

Bien. Et n'oubliez pas que vous payez pour jouer dans un monde de jeux. Vous n'obtenez qu'autant de ctors générés que vous appelez.

J'imagine que l'utilisation de génériques pourrait multiplier cela par un petit facteur.

C'est vrai en général pour les types génériques qui sont construits avec des types de valeurs.

mais l'impact sur l'ensemble du travail semble encore assez faible.

Encore une fois, le jeu de travail est payant. Le code qui n'est appelé que quelques fois finit par être paginé, et beaucoup de constructeurs ne sont appelés que quelques fois dans un programme.

Est-ce que je rate quelque chose ici, et si oui, quoi ?

Pas à ma connaissance.

initialiser les champs là où ils sont déclarés semble produire un code beaucoup plus lisible que l'utilisation d'un constructeur centralisé comme le suggère Richter.

J'optimiserais d'abord la correction et l'élégance, et ne contournerais ce problème que si un test de performance empirique montrait clairement qu'il s'agissait d'un problème réel, ayant un impact sur l'utilisateur.

2voto

Rowie Points 78

Tout d'abord, je tiens à souligner que certaines parties de cette réponse sont tirées de l'université en '99-01 (qui traitait du code procédural plutôt que de la POO) et des morceaux que j'ai pris au fil du temps pas nécessairement tous à faire avec c# donc je ne suis pas sûr des chiffres exacts par rapport à c# donc tout nombre commençant par ~ peut ne pas être exact (dans le cas de 16b pour les constructeurs, je suppose que c'est en fait 12b + 1b par paramètre, bien que ce ne soit pas le cas pour c#), Cependant, pour les besoins de la démonstration, ils ne devraient pas être importants.

Habituellement, lorsque vous créez un objet, vous savez quelles informations sont nécessaires et vous êtes en mesure de créer un modèle vierge ou de donner quelques valeurs pour produire ce que vous voulez, éventuellement en le construisant par enchaînement de constructeurs, mais que se passe-t-il si, lorsque vous créez vos instances, vous ne connaissez pas toutes les valeurs ou ne pouvez pas enchaîner vos constructeurs ? L'exemple ci-dessous montre une classe qui va créer une instance d'un objet d'un modèle donné en fonction des valeurs que vous lui donnez.

class ObjOfSetDesign
{
 int A, B, C, D, E, F;
 int X = 90;
 int Y = 45;
 int Z = 75;

public ObjOfSetDesign(int a)
{
 A = a;
 D = a / 5;
 B = (A + D) * 2;
 C = B * 6;
 E = A * 4;
 F = C / 2;
}

public ObjOfSetDesign(int b, int c)
{
 B = b;
 C = c;
 D = b / 12;
 A = D * 5;
 E = A * 4;
 F = c / 2;
}

public ObjOfSetDesign (int d, int e, int f)
{
 D = d;
 E = e;
 F = f;
 A = d * 5;
 C = f * 2;
 B = c / 6;
}

Évidemment, ce n'est pas le meilleur exemple, vous pourriez calculer A et utiliser ce constructeur à chaque fois, mais si votre classe a 100 champs ou si certaines variables utilisent XYZ pour calculer leur valeur, vous n'aurez peut-être pas ce luxe. Cependant, aucun des constructeurs n'a quelque chose en commun si ce n'est que les valeurs peuvent être utilisées pour calculer les autres, dans le cas du deuxième constructeur, vous ne pouvez même pas utiliser b et c pour appeler :this(a) non plus.

Ajoutez à cela le fait que tous ces champs ne sont pas forcément des ints, vous pouvez avoir des listes ou passer une instance d'une classe personnalisée dans le constructeur pour créer votre objet et j'espère que cela vous aide à voir comment une classe peut avoir 100 champs avec 10 constructeurs indépendants, et que chacun d'entre eux aura XYZ injecté au début du constructeur.

Pour ce qui est de la manière dont cela pourrait être significatif, vous arrivez à 16kb dans votre exemple, cela couvrirait le code pour créer un modèle mais AFAIK devrait également contenir les données elles-mêmes. Si la moitié de vos champs sont initialisés en tant qu'ints, cela représente 100 ko supplémentaires, mais si vous aviez une liste, vous auriez ~22 ko pour créer la liste, 1 ko pour chaque index et les données qu'elle contient. Si votre liste contient des listes, la même chose s'appliquerait pour chaque liste contenue également, ce qui m'amène à la prochaine classe à considérer.

class BiggerObj
{
 ObjOfSetDesign Obj1 = new ObjOfSetDesign(5);
 ObjOfSetDesign Obj2 = new ObjOfSetDesign(10);
 ObjOfSetDesign Obj3 = new ObjOfSetDesign(15);

 ObjOfSetDesign Obj4;
 ObjOfSetDesign Obj5;

 public BiggerObj(int a4, int a5)
 {
  Obj4 = new ObjOfSetDesign(a4);
  Obj5 = new ObjOfSetDesign(a5);
 }

 public BiggerObj(int b4, int c4, int b5, int c5)
 {
  Obj4 = new ObjOfSetDesign(b4, c4);
  Obj5 = new ObjOfSetDesign(b5, c5);
 }
 publ BiggerObj(int d4, int e4, int f4, int d5, int e5, int f5)
 {
  Obj4 = new ObjOfSetDesign(d4, e4, f4);
  Obj5 = new ObjOfSetDesign(d5, e5, f5);
 }
}

Dans cet exemple, notre ObjOfSetDesign est maintenant injecté au début du constructeur de notre BiggerObj avec le code pour le créer, y compris nos 3 constructeurs et XYZ qui est maintenant injecté 9 fois. Voici comment je calcule l'addition.

Dans ObjOfSetDesign vous avez :

  • 9 champs = 9b
  • 3 ints initialisés = 6b
  • 3 constructeurs (~16b x 3) = 48b + 18b injecté = 66b

Totalisation à 81b pour créer un objet contenant 18b de données.

Dans BiggerObj vous avez :

  • 5 champs = 5b
  • 3 initialisé ObjOfSetDesign = (75b x 3) 225b
  • 3 constructeurs (~16b x 3) = 48b + 675b injectés = 723b

Totalisant à 953b pour créer un objet contenant 90b

Et cela n'inclut même pas le coût de l'appel des fonctions à l'intérieur de chaque constructeur. Si dans les deux cas nous avions appelé un constructeur commun pour initialiser les champs prédéfinis, ObjOfSetDesign serait de 73b et BiggerObj serait de 69b, plus la taille du code de nos constructeurs (je ne l'ai pas inclus car il ne changerait pas, à part qu'il y aurait un constructeur supplémentaire qui s'équilibrerait avec la méthode originale d'initialisation à la déclaration) et contiendrait toujours la même quantité de données.

À plus grande échelle, comme votre classe de 100 champs, surtout s'ils sont imbriqués comme ci-dessus, cela pourrait aboutir à la création d'un objet qui insère plus de 5 Mo de code alors qu'il ne doit faire que 50 Ko au maximum.

J'espère que cela vous aidera et si quelque chose ne s'applique pas à C#, faites-le moi savoir pour que je puisse le corriger.

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