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)?

Est-ce que quelqu’un connaît la réponse et / ou a un avis à ce sujet?

Comme les tuples ne seraient normalement pas très volumineux, je suppose que cela aurait plus de sens d’utiliser des structures que des classes pour celles-ci. Ce que vous dites?

Microsoft a fait de tous les types de tuple des types de référence 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 devraient être remplacés par une alternative plus typée (comme un type d’enregistrement en F #), de sorte que seuls les petits tuples présentent un intérêt pratique. Mes propres tests ont montré que les tuples sans boîte contenant jusqu’à 512 octets pouvaient encore être plus rapides que les tuples en boîte.

Bien que l’efficacité de la mémoire soit une préoccupation, j’estime que le problème principal est la surcharge du ramasse-miettes .NET. L’allocation et la collecte sont très coûteuses sur .NET car son ramasse-miettes n’a pas été très optimisé (par exemple, comparé à la JVM). De plus, le .NET par défaut (poste de travail) n’a pas encore été parallélisé. Par conséquent, les programmes parallèles utilisant des tuples s’arrêtent lorsque tous les cœurs se disputent le collecteur de la mémoire partagé, ce qui détruit l’évolutivité. Ce n’est pas seulement le problème dominant, mais AFAIK a été complètement négligé par Microsoft lorsqu’il a examiné ce problème.

Une autre préoccupation est la répartition virtuelle. 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 une répartition virtuelle. En revanche, les types de valeur ne peuvent pas prendre en charge les sous-types, de sorte que l’invocation des membres est totalement non ambiguë et peut toujours être effectuée en tant qu’appel de fonction directe. La dissortingbution virtuelle est extrêmement coûteuse sur le matériel moderne, car le processeur ne peut pas prédire où le compteur de programmes va se retrouver. La JVM met tout en œuvre pour optimiser la répartition virtuelle, mais pas .NET. Cependant, .NET fournit une échappatoire à la dissortingbution virtuelle sous la forme de types de valeur. La représentation des tuples en tant que types de valeurs pourrait donc, à nouveau, considérablement améliorer les performances. Par exemple, appeler GetHashCode sur un tuple un million de fois prend 0,17, mais l’appel sur une structure équivalente ne prend que 0,008, c’est-à-dire que le type de valeur est 20 fois plus rapide que le type de référence.

Une situation réelle où ces problèmes de performance 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é plus lent que son Python précisément parce qu’il utilisait des tuples en boîte. Unboxing manuel utilisant un type de struct écrit à la main 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 par …

La raison est très probablement due au fait que seuls les n-uplets les plus petits seraient utiles en tant que types de valeur, car ils auraient une faible empreinte mémoire. Les plus gros tuples (c.-à-d. Ceux qui ont plus de propriétés) souffriraient effectivement de performances car ils seraient plus grands que 16 octets.

Plutôt que de faire en sorte que certains tuples soient des types de valeur et que d’autres soient des types de référence et obligent les développeurs à savoir quels sont ceux qui, selon moi, Microsoft pensaient en faire tous les types de référence plus simples.

Ah, les soupçons confirmés! S’il vous plaît voir bâtiment Tuple :

La première décision majeure consistait à savoir s’il fallait traiter les tuples comme référence ou comme type de valeur. Comme ils sont immuables chaque fois que vous voulez changer les valeurs d’un tuple, vous devez en créer un nouveau. Si ce sont des 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 n-uplets F # étaient des types de référence, mais l’équipe avait le sentiment de pouvoir améliorer les performances si deux et peut-être trois tuples d’éléments étaient plutôt des types de valeur. Certaines équipes qui avaient créé des tuples internes avaient utilisé la valeur plutôt que des types de référence, car leurs scénarios étaient très sensibles à la création de nombreux objects 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 de tuple, nous avons conservé les tuples à deux, trois et quatre éléments comme types de valeur, le rest étant des types de référence. Cependant, lors d’une réunion de conception comprenant des représentants d’autres langues, il a été décidé que cette conception “fractionné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 aux augmentations de performance potentielles. Sur la base de cette entrée, nous avons modifié la conception pour que tous les n-uplets soient des types de référence, bien que nous ayons demandé à l’équipe F # de vérifier si elle utilisait un type de valeur pour certains n-uplets. C’était un bon moyen de tester cela, car son compilateur, écrit en F #, était un bon exemple de programme volumineux qui utilisait des tuples dans divers scénarios. Au final, l’équipe F # a constaté qu’elle n’avait pas d’amélioration des performances lorsque certains tuples étaient des types de valeur plutôt que des types de référence. Cela nous a fait sentir mieux dans notre décision d’utiliser des types de référence pour le tuple.

Si les types .NET System.Tuple <...> étaient définis en tant que structures, ils ne seraient pas évolutifs. Par exemple, un tuple ternaire d’entiers longs s’échelonne actuellement comme suit:

 type Tuple3 = System.Tuple type Tuple33 = System.Tuple sizeof // Gets 4 sizeof // 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 // Would get 32 sizeof // Would get 104 

Comme les tuples ont un support de syntaxe intégré dans F #, et qu’ils sont utilisés très souvent dans ce langage, les tuples “struct” poseraient le risque aux programmeurs F # 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 significatives dans la programmation quotidienne. D’autre part, les tuples de “classe” existants provoquent également certaines inefficacités, comme mentionné par @Jon. Cependant, je pense que le produit de “probabilité d’apparition” fois “dommage potentiel” serait beaucoup plus élevé avec les structures qu’il ne l’est actuellement 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 deux supportant la syntaxe dans F #!

Modifier (2017-10-07)

Les tuples de structure sont maintenant entièrement supportés comme suit:

  • Construit dans mscorlib (.NET> = 4.7) en tant que System.ValueTuple
  • Disponible en version NuGet pour les autres versions
  • Prise en charge syntaxique en C #> = 7
  • Prise en charge syntaxique dans F #> = 4.1

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

Une précision mineure à 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’immutabilité est en vigueur (ce qui, bien sûr, serait le cas ici). Néanmoins, je pense qu’il aurait été préférable de ne pas introduire dans la conception de la BCL la confusion consistant à faire en sorte que Tuple se croise avec un type de référence à un certain seuil.

Je ne sais pas mais si vous avez déjà utilisé F # Tuples font partie de la langue. Si j’ai créé un fichier .dll et renvoyé un type de tuples, il serait intéressant d’avoir un type pour le mettre. Je pense que F # fait partie de la langue (.Net 4). en F #

De http://fr.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)