web-dev-qa-db-fra.com

Structures vs classes

Je suis sur le point de créer 100 000 objets en code. Ce sont des petits, avec seulement 2 ou 3 propriétés. Je les mettrai dans une liste générique et quand ils le seront, je les bouclerai et vérifierai la valeur a et peut-être mettre à jour la valeur b.

Est-il plus rapide/préférable de créer ces objets en classe ou en structure?

MODIFIER

une. Les propriétés sont des types de valeur (sauf la chaîne je pense?)

b. Ils pourraient (nous ne sommes pas encore sûrs) avoir une méthode de validation

MODIFIER 2

Je me demandais: les objets sur le tas et la pile sont-ils traités également par le garbage collector, ou cela fonctionne-t-il différemment?

87
Michel

Est-ce plus rapide pour créer ces objets en classe ou en struct?

Vous êtes la seule personne à pouvoir déterminer la réponse à cette question. Essayez-le dans les deux sens, mesure une mesure de performance pertinente, axée sur l'utilisateur et pertinente, et vous saurez alors si le changement a un effet significatif sur les utilisateurs réels dans les scénarios pertinents.

Les structures consomment moins de mémoire de tas (car elles sont plus petites et plus facilement compactées, non pas parce qu'elles sont "sur la pile"). Mais leur copie prend plus de temps qu'une copie de référence. Je ne sais pas quelles sont vos mesures de performances pour l'utilisation de la mémoire ou la vitesse; il y a un compromis ici et vous êtes la personne qui sait ce que c'est.

Est-ce mieux pour créer ces objets en classe ou en struct?

Peut-être classe, peut-être struct. En règle générale: si l'objet est:
1. Petit
2. Logiquement une valeur immuable
3. Il y en a beaucoup
J'envisagerais alors d'en faire une structure. Sinon, je m'en tiendrai à un type de référence.

Si vous devez muter un champ d'une structure, il est généralement préférable de construire un constructeur qui renvoie une nouvelle structure entière avec le champ correctement défini. C'est peut-être un peu plus lent (mesurez-le!) Mais logiquement beaucoup plus facile à raisonner.

Les objets du tas et de la pile sont-ils traités également par le garbage collector?

Non, ce ne sont pas les mêmes car les objets de la pile sont les racines de la collection . Le garbage collector n'a pas besoin de se demander "est-ce que cette chose sur la pile est vivante?" parce que la réponse à cette question est toujours "Oui, c'est sur la pile". (Maintenant, vous ne pouvez pas compter sur cela pour garder un objet vivant car la pile est un détail d'implémentation. La gigue est autorisée à introduire des optimisations qui, disons, enregistre ce qui serait normalement une valeur de pile, et ensuite ce n'est jamais sur la pile donc le GC ne sait pas qu'il est toujours vivant. ne va pas être relu.)

Mais le garbage collector doit traiter les objets de la pile comme vivants, de la même manière qu'il traite tout objet connu pour être vivant comme vivant. L'objet sur la pile peut faire référence à des objets alloués en tas qui doivent être maintenus en vie, de sorte que le GC doit traiter les objets de la pile comme des objets alloués en tas vivants afin de déterminer l'ensemble vivant. Mais évidemment, ils ne sont pas traités comme des "objets vivants" dans le but de compacter le tas, car ils ne sont pas sur le tas en premier lieu.

Est-ce clair?

127
Eric Lippert

Parfois, avec struct, vous n'avez pas besoin d'appeler le constructeur new () et d'affecter directement les champs, ce qui le rend beaucoup plus rapide que d'habitude.

Exemple:

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i].id = i;
    list[i].is_valid = true;
}

est environ 2 à 3 fois plus rapide que

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i] = new Value(i, true);
}

Value est un struct avec deux champs (id et is_valid).

D'un autre côté, les éléments doivent être déplacés ou des types de valeur sélectionnés tout ce que la copie va vous ralentir. Pour obtenir la réponse exacte, je suppose que vous devez profiler votre code et le tester.

22
ja72

Les structures peuvent sembler similaires aux classes, mais il y a des différences importantes dont vous devez être conscient. Tout d'abord, les classes sont des types de référence et les structures sont des types de valeur. En utilisant des structures, vous pouvez créer des objets qui se comportent comme les types intégrés et profiter également de leurs avantages.

Lorsque vous appelez l'opérateur New sur une classe, il sera alloué sur le tas. Cependant, lorsque vous instanciez une structure, elle est créée sur la pile. Cela entraînera des gains de performances. De plus, vous ne traiterez pas des références à une instance d'une structure comme vous le feriez avec des classes. Vous travaillerez directement avec l'instance struct. Pour cette raison, lors du passage d'un struct à une méthode, il est passé par valeur au lieu de comme référence.

Plus ici:

http://msdn.Microsoft.com/en-us/library/aa288471 (VS.71) .aspx

7
kyndigs

Les tableaux de structures sont représentés sur le tas dans un bloc de mémoire contigu, tandis qu'un tableau d'objets est représenté comme un bloc de références contigu avec les objets réels eux-mêmes ailleurs sur le tas, nécessitant ainsi de la mémoire pour les objets et pour leurs références de tableau .

Dans ce cas, lorsque vous les placez dans un List<> (et un List<> est sauvegardé sur un tableau), il serait plus efficace, en termes de mémoire, d'utiliser des structures.

(Attention cependant, les grands tableaux trouveront leur chemin sur le tas d'objets volumineux où, si leur durée de vie est longue, peut avoir un effet négatif sur la gestion de la mémoire de votre processus. N'oubliez pas que cette mémoire n'est pas la seule considération.)

6
Paul Ruane

S'ils ont une sémantique de valeur, vous devriez probablement utiliser une structure. S'ils ont une sémantique de référence, vous devriez probablement utiliser une classe. Il existe des exceptions, qui visent principalement à créer une classe même lorsqu'il existe une sémantique de valeur, mais partent de là.

Quant à votre deuxième édition, le GC ne traite que du tas, mais il y a beaucoup plus d'espace de tas que d'espace de pile, donc mettre des choses sur la pile n'est pas toujours une victoire. En plus de cela, une liste de struct-types et une liste de classe-types seront sur le tas de toute façon, donc cela n'a pas d'importance dans ce cas.

Modifier:

Je commence à considérer le terme evil comme nuisible. Après tout, rendre une classe mutable est une mauvaise idée si elle n'est pas activement nécessaire, et je n'exclurais jamais d'utiliser une structure mutable. C'est une mauvaise idée si souvent qu'elle est presque toujours une mauvaise idée, mais surtout elle ne coïncide tout simplement pas avec la sémantique des valeurs, il n'est donc pas logique d'utiliser une structure dans le cas donné.

Il peut y avoir des exceptions raisonnables avec des structures imbriquées privées, où toutes les utilisations de cette structure sont donc limitées à une portée très limitée. Cela ne s'applique cependant pas ici.

Vraiment, je pense que "ça mute donc c'est un mauvais stuct" n'est pas beaucoup mieux que de parler du tas et de la pile (ce qui a au moins un impact sur les performances, même s'il est souvent déformé). "Il mute, donc tout à fait probable n'a pas de sens de le considérer comme ayant une sémantique de valeur, donc c'est une mauvaise structure" n'est que légèrement différent, mais surtout je pense.

4
Jon Hanna

Une structure n'est, en son cœur, ni plus ni moins qu'une agrégation de champs. Dans .NET, il est possible pour une structure de "faire semblant" d'être un objet, et pour chaque type de structure .NET définit implicitement un type d'objet de tas avec les mêmes champs et méthodes qui - étant un objet de tas - se comporteront comme un objet . Une variable qui contient une référence à un tel objet tas (structure "en boîte") présentera une sémantique de référence, mais celle qui contient directement une structure est simplement une agrégation de variables.

Je pense qu'une grande partie de la confusion entre la structure et la classe vient du fait que les structures ont deux cas d'utilisation très différents, qui devraient avoir des directives de conception très différentes, mais les directives MS ne les distinguent pas. Parfois, il faut quelque chose qui se comporte comme un objet; dans ce cas, les directives MS sont assez raisonnables, bien que la "limite de 16 octets" devrait probablement ressembler davantage à 24-32. Parfois, cependant, ce qui est nécessaire est une agrégation de variables. Une structure utilisée à cet effet doit simplement consister en un tas de champs publics, et éventuellement une surcharge Equals, ToString et une implémentation IEquatable(itsType).Equals. Les structures qui sont utilisées comme agrégations de champs ne sont pas des objets et ne devraient pas prétendre l'être. Du point de vue de la structure, la signification du champ ne doit être ni plus ni moins que "la dernière chose écrite dans ce champ". Toute signification supplémentaire doit être déterminée par le code client.

Par exemple, si une structure d'agrégation de variables a des membres Minimum et Maximum, la structure elle-même ne devrait pas promettre que Minimum <= Maximum. Le code qui reçoit une telle structure en tant que paramètre doit se comporter comme s'il avait reçu des valeurs Minimum et Maximum distinctes. Une exigence selon laquelle Minimum ne doit pas être supérieur à Maximum doit être considérée comme une exigence selon laquelle un paramètre Minimum ne doit pas être supérieur à un paramètre Maximum transmis séparément.

Un modèle utile à considérer parfois est d'avoir un ExposedHolder<T> la classe a défini quelque chose comme:

class ExposedHolder<T>
{
  public T Value;
  ExposedHolder() { }
  ExposedHolder(T val) { Value = T; }
}

Si l'on a un List<ExposedHolder<someStruct>>, où someStruct est une structure d'agrégation de variables, on peut faire des choses comme myList[3].Value.someField += 7;, mais en donnant myList[3].Value à un autre code lui donnera le contenu de Value plutôt que de lui donner un moyen de le modifier. En revanche, si l'on utilise un List<someStruct>, il faudrait utiliser var temp=myList[3]; temp.someField += 7; myList[3] = temp;. Si l'on utilise un type de classe mutable, exposer le contenu de myList[3] vers du code extérieur nécessiterait de copier tous les champs dans un autre objet. Si l'on utilisait un type de classe immuable, ou une structure "style objet", il serait nécessaire de construire une nouvelle instance qui ressemblerait à myList[3] à l'exception de someField qui était différent, puis stockez cette nouvelle instance dans la liste.

Une remarque supplémentaire: si vous stockez un grand nombre de choses similaires, il peut être bon de les stocker dans des tableaux de structures éventuellement imbriqués, en essayant de préférence de garder la taille de chaque tableau entre 1K et 64K environ. Les tableaux de structures sont spéciaux, dans la mesure où l'indexation donne une référence directe à une structure interne, on peut donc dire "a [12] .x = 5;". Bien que l'on puisse définir des objets de type tableau, C # ne leur permet pas de partager une telle syntaxe avec des tableaux.

3
supercat

La meilleure solution est de mesurer, de mesurer à nouveau, puis de mesurer un peu plus. Il peut y avoir des détails sur ce que vous faites qui peuvent rendre difficile une réponse simplifiée et facile comme "utiliser des structures" ou "utiliser des classes".

3
FMM

Utilisez des classes.

D'une manière générale. Pourquoi ne pas mettre à jour la valeur b lorsque vous les créez?

1
Preet Sangha

D'un point de vue c ++, je conviens qu'il sera plus lent de modifier les propriétés d'une structure par rapport à une classe. Mais je pense qu'ils seront plus rapides à lire en raison de la structure allouée sur la pile au lieu du tas. La lecture des données du tas nécessite plus de vérifications que de la pile.

1
Robert

Eh bien, si vous optez pour la structure après tout, débarrassez-vous de la chaîne et utilisez un char de taille fixe ou un tampon d'octets.

C'est re: la performance.

1
Daniel Mošmondor