web-dev-qa-db-fra.com

Pourquoi le nouveau type de tuple dans .Net 4.0 est-il un type de référence (classe) et non un type de valeur (struct)

Quelqu'un connaît-il la réponse et/ou a-t-il une opinion à ce sujet?

Comme les tuples ne seraient normalement pas très grands, je suppose qu'il serait plus logique d'utiliser des structures que des classes pour celles-ci. Ce que vous dites?

87
Bent Rasmussen

Microsoft a fait tous les types de référence de types Tuple dans un souci de simplicité.

Je pense personnellement que c'était une erreur. Les tuples avec plus de 4 champs sont très inhabituels et doivent de toute façon être remplacés par une alternative plus typée (comme un type d'enregistrement en F #) donc seuls les petits tuples sont d'un intérêt pratique. Mes propres tests de performance ont montré que les tuples sans boîte jusqu'à 512 octets pouvaient toujours être plus rapides que les tuples en boîte.

Bien que l'efficacité de la mémoire soit une préoccupation, je pense que le problème dominant est la surcharge du garbage collector .NET. L'allocation et la collecte sont très chères sur .NET car son garbage collector n'a pas été très fortement optimisé (par exemple par rapport à la JVM). De plus, le GC (poste de travail) .NET par défaut n'a pas encore été parallélisé. Par conséquent, les programmes parallèles qui utilisent des tuples s'arrêtent car tous les cœurs se disputent le garbage collector partagé, détruisant l'évolutivité. Ce n'est pas seulement la préoccupation dominante mais, AFAIK, a été complètement négligé par Microsoft lors de l'examen de ce problème.

Une autre préoccupation est l'envoi virtuel. Les types de référence prennent en charge les sous-types et, par conséquent, leurs membres sont généralement appelés via la répartition virtuelle. En revanche, les types de valeur ne peuvent pas prendre en charge les sous-types, donc l'invocation de membre est entièrement sans ambiguïté et peut toujours être effectuée en tant qu'appel de fonction direct. L'envoi virtuel coûte énormément cher sur du matériel moderne car le CPU ne peut pas prédire où le compteur de programme se terminera. La JVM met tout en œuvre pour optimiser la répartition virtuelle, mais pas .NET. Cependant, .NET fournit une échappatoire à la répartition virtuelle sous la forme de types de valeur. Ainsi, la représentation des tuples en tant que types de valeur pourrait, là encore, avoir considérablement amélioré les performances ici. Par exemple, appeler GetHashCode sur un 2-Tuple un million de fois prend 0,17s mais l'appeler sur une structure équivalente ne prend que 0,008s, c'est-à-dire que le type de valeur est 20 × plus rapide que le type de référence.

Une situation réelle où ces problèmes de performances avec les tuples surviennent généralement est l'utilisation de tuples comme clés dans les dictionnaires. En fait, je suis tombé sur ce fil en suivant un lien de la question Stack Overflow F # exécute mon algorithme plus lentement que Python! où le programme F # de l'auteur s'est avéré être plus lent que son Python _ précisément parce qu'il utilisait des tuples en boîte. Unboxing manuel utilisant un type struct manuscrit rend son programme F # plusieurs fois plus rapide et plus rapide que Python. Ces problèmes ne se seraient jamais posés si les tuples étaient représentés par des types de valeur et non des types de référence pour commencer ...

92
Jon Harrop

La raison en est probablement que seuls les tuples plus petits auraient un sens en tant que types de valeur car ils auraient une petite empreinte mémoire. Les tuples plus gros (c'est-à-dire ceux avec plus de propriétés) souffriraient en fait de performances car ils seraient plus grands que 16 octets.

Plutôt que d'avoir des tuples soit des types de valeur et d'autres des types de référence et obliger les développeurs à savoir lesquels, j'imagine que les gens de Microsoft pensaient que leur faire tous les types de référence était plus simple.

Ah, les soupçons confirmés! Veuillez voir Building Tuple :

La première décision majeure a été de savoir si les tuples devaient être traités comme un type de référence ou de valeur. Puisqu'ils sont immuables chaque fois que vous souhaitez modifier les valeurs d'un Tuple, vous devez en créer un nouveau. S'il s'agit de types de référence, cela signifie qu'il peut y avoir beaucoup de déchets générés si vous modifiez des éléments dans un tuple dans une boucle serrée. Les tuples F # étaient des types de référence, mais l'équipe avait le sentiment qu'ils pouvaient réaliser une amélioration des performances si deux, et peut-être trois, les tuples d'éléments étaient plutôt des types de valeur. Certaines équipes qui avaient créé des tuples internes avaient utilisé de la valeur au lieu de types de référence, car leurs scénarios étaient très sensibles à la création de nombreux objets gérés. Ils ont constaté que l'utilisation d'un type de valeur leur donnait de meilleures performances. Dans notre première version de la spécification Tuple, nous avons conservé les tuples à deux, trois et quatre éléments comme types de valeur, le reste étant des types de référence. Cependant, lors d'une réunion de conception qui comprenait des représentants d'autres langues, il a été décidé que cette conception "divisée" serait source de confusion, en raison de la sémantique légèrement différente entre les deux types. La cohérence du comportement et de la conception a été jugée prioritaire par rapport à l'augmentation potentielle des performances. Sur la base de cette entrée, nous avons modifié la conception afin que tous les tuples soient des types de référence, bien que nous ayons demandé à l'équipe F # de faire une enquête sur les performances pour voir si elle a connu une accélération lors de l'utilisation d'un type de valeur pour certaines tailles de tuples. Il avait un bon moyen de tester cela, car son compilateur, écrit en F #, était un bon exemple d'un grand programme qui utilisait des tuples dans une variété de scénarios. En fin de compte, l'équipe F # a constaté qu'il n'y avait pas d'amélioration des performances lorsque certains tuples étaient des types de valeur au lieu de types de référence. Cela nous a fait nous sentir mieux dans notre décision d'utiliser des types de référence pour Tuple.

43
Andrew Hare

Si les types .NET System.Tuple <...> étaient définis comme des structures, ils ne seraient pas évolutifs. Par exemple, un tuple ternaire d'entiers longs évolue actuellement comme suit:

type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4

Si le tuple ternaire était défini comme une structure, le résultat serait le suivant (basé sur un exemple de test que j'ai implémenté):

sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104

Comme les tuples ont un support de syntaxe intégré en F #, et ils sont utilisés très souvent dans ce langage, les tuples "struct" exposeraient les programmeurs F # au risque d'écrire des programmes inefficaces sans même s'en rendre compte. Cela arriverait si facilement:

let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3

À mon avis, les tuples "struct" entraîneraient une forte probabilité de créer des inefficacités importantes dans la programmation quotidienne. D'un autre côté, les tuples de "classe" actuellement existants provoquent également certaines inefficacités, comme mentionné par @Jon. Cependant, je pense que le produit de la "probabilité d'occurrence" multiplié par "dommages potentiels" serait beaucoup plus élevé avec les structures qu'avec les classes. Par conséquent, la mise en œuvre actuelle est le moindre mal.

Idéalement, il y aurait à la fois des tuples "class" et des tuples "struct", tous les deux avec un support syntaxique en F #!

Modifier (2017-10-07)

Les tuples de structure sont désormais entièrement pris en charge comme suit:

7
Marc Sigrist

Pour les 2 tuples, vous pouvez toujours utiliser le KeyValuePair <TKey, TValue> des versions antérieures du Common Type System. C'est un type de valeur.

Une clarification mineure de l'article de Matt Ellis serait que la différence de sémantique d'utilisation entre les types de référence et de valeur n'est que "légère" lorsque l'immuabilité est en vigueur (ce qui, bien sûr, serait le cas ici). Néanmoins, je pense qu'il aurait été préférable dans la conception BCL de ne pas introduire la confusion d'avoir Tuple croisé vers un type de référence à un certain seuil.

4
Glenn Slayden

Je ne sais pas, mais si vous avez déjà utilisé F #, les tuples font partie de la langue. Si j'ai fait un .dll et retourné un type de Tuples, ce serait bien d'avoir un type pour le mettre dedans. Je soupçonne maintenant que F # fait partie du langage (.Net 4) quelques modifications ont été apportées au CLR pour s'adapter à certaines structures communes en F #

De http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records

let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;

val scalarMultiply : float -> float * float * float -> float * float * float

scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)
0
Bionic Cyborg