La forme API a été finalisée. Cependant, nous sommes toujours en train de décider du meilleur algorithme de hachage parmi une liste de candidats à utiliser pour la mise en œuvre, et nous avons besoin de quelqu'un pour nous aider à mesurer le débit/la distribution de chaque algorithme. Si vous souhaitez assumer ce rôle, veuillez laisser un commentaire ci-dessous et @karelz vous attribuera ce problème.
Voici l'API qui a été approuvée par @terrajobst à l' adresse https://github.com/dotnet/corefx/issues/14354#issuecomment -308190321 :
// Will live in the core assembly
// .NET Framework : mscorlib
// .NET Core : System.Runtime / System.Private.CoreLib
namespace System
{
public struct HashCode
{
public static int Combine<T1>(T1 value1);
public static int Combine<T1, T2>(T1 value1, T2 value2);
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3);
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4);
public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5);
public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6);
public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7);
public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8);
public void Add<T>(T value);
public void Add<T>(T value, IEqualityComparer<T> comparer);
[Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)]
[EditorBrowsable(Never)]
public override int GetHashCode();
public int ToHashCode();
}
}
Le texte original de cette proposition suit.
La génération d'un bon code de hachage ne devrait pas nécessiter l'utilisation de constantes magiques laides et de peu de manipulation sur notre code. Il devrait être moins tentant d'écrire une implémentation GetHashCode
mauvaise mais concise telle que
class Person
{
public override int GetHashCode() => FirstName.GetHashCode() + LastName.GetHashCode();
}
Nous devrions ajouter un type HashCode
pour encapsuler la création de code de hachage et éviter de forcer les développeurs à se mêler aux détails désordonnés. Voici ma proposition, qui est basée sur https://github.com/dotnet/corefx/issues/14354#issuecomment -305019329, avec quelques révisions mineures.
// Will live in the core assembly
// .NET Framework : mscorlib
// .NET Core : System.Runtime / System.Private.CoreLib
namespace System
{
public struct HashCode
{
public static int Combine<T1>(T1 value1);
public static int Combine<T1, T2>(T1 value1, T2 value2);
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3);
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4);
public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5);
public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6);
public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7);
public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8);
public void Add<T>(T value);
public void Add<T>(T value, IEqualityComparer<T> comparer);
public void AddRange<T>(T[] values);
public void AddRange<T>(T[] values, int index, int count);
public void AddRange<T>(T[] values, int index, int count, IEqualityComparer<T> comparer);
[Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)]
public override int GetHashCode();
public int ToHashCode();
}
}
Voir @terrajobst commentaire de » à https://github.com/dotnet/corefx/issues/14354#issuecomment -305019329 pour les objectifs de cette API; toutes ses remarques sont valables. Je voudrais cependant souligner ceux-ci en particulier :
Proposition : ajouter la prise en charge de la randomisation par hachage
public static HashCode Randomized<T> { get; } // or CreateRandomized<T>
or
public static HashCode Randomized(Type type); // or CreateRandomized(Type type)
T
ou Type type
est nécessaire pour obtenir le même hachage aléatoire pour le même type.
Proposition : ajouter un support pour les collections
public HashCode Combine<T>(T[] values);
public HashCode Combine<T>(T[] values, IEqualityComparer<T> comparer);
public HashCode Combine<T>(Span<T> values);
public HashCode Combine<T>(Span<T> values, IEqualityComparer<T> comparer);
public HashCode Combine<T>(IEnumerable<T> values);
public HashCode Combine<T>(IEnumerable<T> IEqualityComparer<T> comparer);
Je pense qu'il n'y a pas besoin de surcharges Combine(_field1, _field2, _field3, _field4, _field5)
car le prochain code HashCode.Empty.Combine(_field1).Combine(_field2).Combine(_field3).Combine(_field4).Combine(_field5);
devrait être optimisé en ligne sans appels Combine.
@AlexRadch
Proposition : ajouter un support pour les collections
Oui, cela faisait partie de mon plan éventuel pour cette proposition. Je pense qu'il est important de se concentrer sur la façon dont nous voulons que l'API ressemble avant d'ajouter ces méthodes, cependant.
Il voulait utiliser un algorithme différent, comme le hachage Marvin32 qui est utilisé pour les chaînes dans coreclr. Cela nécessiterait d'étendre la taille de HashCode à 8 octets.
Qu'en est-il des types Hash32 et Hash64 qui stockeraient en interne 4 ou 8 octets de données ? Documentez les avantages/inconvénients de chacun. Hash64 est bon pour X, mais potentiellement plus lent. Hash32 étant plus rapide, mais potentiellement pas aussi distribué (ou quel que soit le compromis).
Il voulait randomiser la graine de hachage, afin que les hachages ne soient pas déterministes.
Cela semble être un comportement utile. Mais je pouvais voir des gens vouloir contrôler ça. Alors peut-être qu'il devrait y avoir deux façons de créer le Hash, une qui ne prend aucune graine (et utilise une graine aléatoire) et une qui permet à la graine d'être fournie.
Remarque : Roslyn aimerait si cela pouvait être fourni dans le fichier Fx. Nous ajoutons une fonctionnalité pour cracher un GetHashCode pour l'utilisateur. Actuellement, il génère du code comme :
c#
public override int GetHashCode()
{
var hashCode = -1923861349;
hashCode = hashCode * -1521134295 + this.b.GetHashCode();
hashCode = hashCode * -1521134295 + this.i.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.s);
return hashCode;
}
Ce n'est pas une grande expérience, et cela expose de nombreux concepts laids. Nous serions ravis d'avoir une API Hash.Whatever que nous pourrions appeler à la place.
Merci!
Et MurmurHash ? Il est raisonnablement rapide et possède de très bonnes propriétés de hachage. Il existe également deux implémentations différentes, une qui crache des hachages 32 bits et une autre qui crache des hachages 128 bits.
Il existe également des implémentations vectorisées pour les formats 32 bits et 128 bits.
@tannergooding MurmurHash est rapide, mais pas sécurisé, à partir des sons de cet article de blog .
@jkotas , y a-t-il eu des travaux dans le JIT pour générer un meilleur code pour les structures > 4 octets sur 32 bits depuis nos discussions l'année dernière ? Aussi, que pensez-vous de la proposition de
Qu'en est-il des types Hash32 et Hash64 qui stockeraient en interne 4 ou 8 octets de données ? Documentez les avantages/inconvénients de chacun. Hash64 est bon pour X, mais potentiellement plus lent. Hash32 étant plus rapide, mais potentiellement pas aussi distribué (ou quel que soit le compromis).
Je pense toujours que ce type serait très précieux à proposer aux développeurs et ce serait formidable de l'avoir en 2.0.
@jamesqo , je ne pense pas que cette implémentation
De plus, cet article s'applique à Murmur2. Le problème a été résolu dans l'algorithme Murmur3.
le JIT autour de la génération d'un meilleur code pour les structures > 4 octets sur 32 bits depuis nos discussions l'année dernière
Je n'en connais aucun.
que pensez-vous de la proposition de
Les types de framework doivent être des choix simples qui fonctionnent bien pour 95%+ des cas. Ce ne sont peut-être pas les plus rapides, mais c'est bien. Avoir le choix entre Hash32 et Hash64 n'est pas un choix simple.
Ça me va. Mais pouvons-nous au moins avoir une solution suffisamment bonne pour ces 95 % de cas ? Pour l'instant il n'y a rien... :-/
hashCode = hashCode * -1521134295 + EqualityComparer
.Default.GetHashCode(this.s);
@CyrusNajmabadi Pourquoi
Pour les non-structs : afin que nous n'ayons pas besoin de vérifier null.
C'est proche de ce que nous générons également pour les types anonymes dans les coulisses. J'optimise le cas des valeurs non nulles connues pour générer du code qui plairait plus aux utilisateurs. Mais ce serait bien d'avoir juste une API intégrée pour cela.
L'appel à EqualityComparer.Default.GetHashCode est comme 10x+ plus cher que la vérification de null... .
L'appel à EqualityComparer.Default.GetHashCode est comme 10x+ plus cher que la vérification de null ..
Cela ressemble à un problème. si seulement il y avait une bonne API de code de hachage, nous pourrions appeler le Fx auquel je pourrais m'en remettre :)
(de plus, nous avons ce problème dans nos types anonymes car c'est également ce que nous générons là-bas).
Je ne sais pas ce que nous faisons pour les tuples, mais je suppose que c'est similaire.
Je ne sais pas ce que nous faisons pour les tuples, mais je suppose que c'est similaire.
System.Tuple
passe par EqualityComparer<Object>.Default
pour des raisons historiques. System.ValueTuple
appelle Object.GetHashCode avec une vérification nulle - https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/ValueTuple.cs#L809.
Oh non. On dirait que tuple peut simplement utiliser "HashHelpers". Cela pourrait-il être exposé afin que les utilisateurs puissent obtenir le même avantage ?
Super. Je suis heureux de faire quelque chose de similaire. J'ai commencé à partir de nos types anonymes parce que je pensais qu'il s'agissait de meilleures pratiques raisonnables. Si non, c'est bien. :)
Mais ce n'est pas pour ça que je suis ici. Je suis ici pour obtenir un système qui combine efficacement les hachages. Si/quand cela peut être fourni, nous passerons volontiers à l'appel au lieu de coder en dur en nombres aléatoires et de combiner nous-mêmes les valeurs de hachage.
Quelle serait la forme d'API qui, selon vous, fonctionnerait le mieux pour le code généré par le compilateur ?
Littéralement, n'importe laquelle des solutions 32 bits qui ont été présentées plus tôt me conviendrait. Heck, les solutions 64 bits me conviennent. Juste une sorte d'API que vous pouvez obtenir et qui dit "je peux combiner des hachages d'une manière raisonnable et produire un résultat raisonnablement distribué".
Je ne peux pas concilier ces déclarations:
Nous avions une structure HashCode immuable de 4 octets. Il avait une méthode Combine(int), qui mélangeait le code de hachage fourni avec son propre code de hachage via un algorithme de type DJBX33X, et renvoyait un nouveau HashCode.
@jkotas ne pensait pas que l'algorithme de type DJBX33X était assez robuste.
Et
Les types de framework doivent être des choix simples qui fonctionnent bien pour 95%+ des cas.
Ne pouvons-nous pas proposer un simple hachage d'accumulation de 32 bits qui fonctionne assez bien dans 95% des cas ? Quels sont les cas qui ne sont pas bien traités ici, et pourquoi pensons-nous qu'ils sont dans le cas à 95 % ?
@jkotas , la performance est-elle vraiment si critique pour ce type ? Je pense qu'en moyenne des choses comme les recherches de table de hachage et cela prendrait beaucoup plus de temps que quelques copies de structure. Si cela s'avère être un goulot d'étranglement, serait-il raisonnable de demander à l'équipe JIT d'optimiser les copies de structure 32 bits après la publication de l'API afin qu'elles soient incitées, plutôt que de bloquer cette API lorsque personne ne travaille sur l'optimisation copies?
Ne pouvons-nous pas proposer un simple hachage d'accumulation de 32 bits qui fonctionne assez bien dans 95% des cas ?
Nous avons été très gravement brûlés par défaut en accumulant le hachage 32 bits pour les chaînes, et c'est pourquoi le hachage Marvin pour les chaînes dans .NET Core - https://github.com/dotnet/corert/blob/87e58839d6629b5f90777f886a2f52d7a99c076f/src/System.Private.CoreLib src/System/Marvin.cs#L25. Je ne pense pas que nous voulions répéter la même erreur ici.
@jkotas , la performance est-elle vraiment si critique pour ce type ?
Je ne pense pas que la performance soit critique. Puisqu'il semble que cette API va être utilisée par du code de compilateur généré automatiquement, je pense que nous devrions préférer un code généré plus petit à son apparence. Le modèle non fluide est un code plus petit.
Nous avons été très gravement brûlés par défaut en accumulant le hachage 32 bits pour la chaîne
Cela ne semble pas être le cas à 95%. Nous parlons de développeurs normaux qui veulent juste un hachage "assez bon" pour tous ces types où ils font des choses manuellement aujourd'hui.
Puisqu'il semble que cette API va être utilisée par du code de compilateur généré automatiquement, je pense que nous devrions préférer un code généré plus petit à son apparence. Le modèle non fluide est un code plus petit.
Ceci n'est pas destiné à être utilisé par le compilateur Roslyn. Ceci est destiné à être utilisé par l'IDE Roslyn lorsque nous aidons les utilisateurs à générer des GetHashCodes pour leurs types. C'est un code que l'utilisateur verra et devra maintenir, et ayant quelque chose de sensé comme :
```c#
return Hash.Combine(this.A?.GetHashCode() ?? 0,
this.B?.GetHashCode() ?? 0,
this.C?.GetHashCode() ?? 0);
is a lot nicer than a user seeing and having to maintain:
```c#
var hashCode = -1923861349;
hashCode = hashCode * -1521134295 + this.b.GetHashCode();
hashCode = hashCode * -1521134295 + this.i.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.s);
return hashCode;
Je veux dire, nous avons déjà ce code dans le Fx :
Nous pensons que c'est assez bon pour les tuples. Je ne comprends pas pourquoi ce serait un tel problème de le rendre disponible pour les utilisateurs qui le souhaitent pour leurs propres types.
Remarque : nous avons même envisagé de le faire à Roslyn :
c#
return (this.A, this.B, this.C).GetHashCode();
Mais maintenant, vous obligez les gens à générer une structure (potentiellement grande) juste pour obtenir une sorte de comportement de hachage par défaut raisonnable.
Nous parlons de développeurs normaux qui veulent juste un hachage "assez bon" pour tous ces types où ils font des choses manuellement aujourd'hui.
Le hachage de chaîne d'origine était un hachage "assez bon" qui fonctionnait bien pour les développeurs normaux. Mais ensuite, il a été découvert que les serveurs Web ASP.NET étaient vulnérables aux attaques DoS, car ils ont tendance à stocker les éléments reçus dans des tables de hachage. Ainsi, le hachage « assez bon » s'est essentiellement transformé en un mauvais problème de sécurité.
Nous pensons que c'est assez bon pour les tuples
Non forcément. Nous avons fait une mesure d'arrêt pour les tuples afin de rendre le code de hachage aléatoire, ce qui nous donne la possibilité de modifier l'algorithme plus tard.
return Hash.Combine(this.A?.GetHashCode() ?? 0, this.B?.GetHashCode() ?? 0, this.C?.GetHashCode() ?? 0);
Cela me semble raisonnable.
Je ne comprends pas votre position. Vous semblez dire deux choses :
Le hachage de chaîne d'origine était un hachage "assez bon" qui fonctionnait bien pour les développeurs normaux. Mais ensuite, il a été découvert que les serveurs Web ASP.NET étaient vulnérables aux attaques DoS, car ils ont tendance à stocker les éléments reçus dans des tables de hachage. Ainsi, le hachage « assez bon » s'est essentiellement transformé en un mauvais problème de sécurité.
Ok, si c'est le cas, alors fournissons un code de hachage qui convient aux personnes qui ont des problèmes de sécurité/DoS.
Les types de framework doivent être des choix simples qui fonctionnent bien pour 95%+ des cas.
Ok, si c'est le cas, alors fournissons un code de hachage qui est assez bon pour les 95% des cas. Les personnes qui ont des problèmes de sécurité/DoS peuvent utiliser les formulaires spécialisés qui sont documentés à cette fin.
Non forcément. Nous avons fait une mesure d'arrêt pour les tuples afin de rendre le code de hachage aléatoire, ce qui nous donne la possibilité de modifier l'algorithme plus tard.
D'accord. Pouvons-nous exposer cela afin que les utilisateurs puissent utiliser ce même mécanisme.
--
J'ai vraiment du mal ici parce qu'on dirait que nous disons "parce que nous ne pouvons pas trouver de solution universelle, tout le monde doit lancer la sienne". Cela semble être l'un des pires endroits où être. Parce que la plupart de nos clients ne pensent certainement pas à lancer leur propre "marvin hash" pour des problèmes de DoS. Ils ne font qu'ajouter, extraire ou combiner mal les hachages de champ en un hachage final.
Si nous nous soucions du cas à 95%, alors nous devrions juste faire un hachage généralement assez bon. SI nous nous soucions du cas des 5%, nous pouvons fournir une solution spécialisée pour cela.
Cela me semble raisonnable.
Super :) Pouvons-nous alors exposer :
```c#
espace de noms System.Numerics.Hashing
{
classe statique interne HashHelpers
{
public static readonly int RandomSeed = new Random().Next(Int32.MinValue, Int32.MaxValue);
public static int Combine(int h1, int h2)
{
// RyuJIT optimizes this to use the ROL instruction
// Related GitHub pull request: dotnet/coreclr#1830
uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
return ((int)rol5 + h1) ^ h2;
}
}
Roslyn could then generate:
```c#
return Hash.Combine(Hash.RandomSeed,
this.A?.GetHashCode() ?? 0,
this.B?.GetHashCode() ?? 0,
this.C?.GetHashCode() ?? 0);
Cela aurait l'avantage d'être vraiment "assez bon" pour la grande majorité des cas, tout en guidant les gens sur la bonne voie d'initialisation avec des valeurs aléatoires afin qu'ils ne prennent pas de dépendances sur des hachages non aléatoires.
Les personnes qui ont des problèmes de sécurité/DoS peuvent utiliser les formulaires spécialisés qui sont documentés à cette fin.
Chaque application ASP.NET a des problèmes de sécurité/DoS.
Super :) Pouvons-nous alors exposer :
C'est différent de ce que j'ai dit est raisonnable.
Que pensez-vous de https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.HashCodeCombiner.Sources/HashCodeCombiner.cs . C'est ce qui est utilisé en interne dans ASP.NET à plusieurs endroits aujourd'hui, et c'est ce dont je serais assez satisfait (sauf que la fonction de combinaison doit être plus forte - des détails d'implémentation que nous pouvons continuer à peaufiner).
@jkotas j'ai entendu ça :p
Donc, le problème ici est que les développeurs ne savent pas quand ils sont sensibles aux attaques DoS, car ce n'est pas quelque chose qu'ils pensent, c'est pourquoi nous avons changé les chaînes pour utiliser Marvin32.
Nous ne devons pas nous engager dans la voie du « 95% des cas n'ont pas d'importance », car nous n'avons aucun moyen de le prouver, et nous devons pécher par excès de prudence même lorsque cela a un coût en termes de performances. Si vous envisagez de vous éloigner de cela, la mise en œuvre du code de hachage nécessite un examen par Crypto Board, et pas seulement nous décidions « Cela semble assez bon ».
Chaque application ASP.NET a des problèmes de sécurité/DoS.
D'accord. Alors, comment gérez-vous le problème aujourd'hui selon lequel personne n'a d'aide avec les hashcodes et fait donc probablement les choses mal? Il est clair qu'il est acceptable d'avoir cet état du monde. Alors, qu'est-ce qui nuit à fournir un système de hachage raisonnable qui fonctionne probablement mieux que ce que les gens roulent à la main aujourd'hui ?
car nous n'avons aucun moyen de le prouver, et nous devons pécher par excès de prudence même lorsque cela a un coût de performance
Si vous ne fournissez pas quelque chose, les gens continueront à mal faire les choses. Le rejet du « assez bien » parce qu'il n'y a rien de parfait signifie simplement le mauvais statu quo que nous avons aujourd'hui.
Chaque application ASP.NET a des problèmes de sécurité/DoS.
Pouvez-vous expliquer cela? Si je comprends bien, vous avez un problème de DoS si vous acceptez des entrées arbitraires et que vous les stockez ensuite dans une structure de données qui fonctionne mal si les entrées peuvent être spécialement conçues. D'accord, je comprends à quel point c'est un problème avec les chaînes que l'on obtient dans les scénarios Web provenant de l'utilisateur.
Alors, comment cela s'applique-t-il aux autres types qui ne sont pas utilisés dans ce scénario ?
Nous avons ces ensembles de types :
Fondamentalement, nous pensons que ces cas sont importants, mais pas assez importants pour fournir réellement une solution aux utilisateurs pour gérer '1' ou '2'. Parce que nous craignons qu'une solution pour '2' ne soit pas bonne pour '1', nous ne la fournirons même pas en premier lieu. Et si nous ne sommes même pas disposés à fournir une solution pour '1', nous avons l'impression d'être dans une position incroyablement étrange. Nous sommes inquiets pour le DoSing et l'ASP, mais pas assez pour aider les gens. Et parce que nous n'aiderons pas les gens avec ça, nous ne sommes même pas disposés à aider avec les cas non DoS.
--
Si ces deux cas sont importants (ce que je suis prêt à accepter), alors pourquoi ne pas simplement donner deux API ? Documentez-les. Expliquez-leur clairement à quoi ils servent. Si les gens les utilisent correctement, grande. Si les gens ne les utilisent pas correctement, c'est toujours bien. Après tout, ils ne font probablement pas les choses correctement aujourd'hui de toute façon , alors comment les choses sont-elles pires ?
Qu'en pensez-vous
Je n'ai pas d'opinion dans un sens ou dans l'autre. S'il s'agit d'une API que les clients peuvent utiliser, qui fonctionne de manière acceptable et qui fournit une API simple avec un code clair de leur côté, alors je pense que c'est bien.
Je pense qu'il serait bien d'avoir un formulaire statique simple qui gère le cas à 99% de vouloir combiner un ensemble de champs/propriétés de manière ordonnée. Il semble qu'une telle chose pourrait être ajoutée à ce type assez simplement.
Je pense que ce serait bien d'avoir une forme statique simple
Se mettre d'accord.
Je pense qu'il serait bien d'avoir un formulaire statique simple qui gère le cas à 99% de vouloir combiner un ensemble de champs/propriétés de manière ordonnée. Il semble qu'une telle chose pourrait être ajoutée à ce type assez simplement.
Se mettre d'accord.
Je suis prêt à vous rencontrer tous les deux à mi-chemin sur celui-ci parce que je veux vraiment voir une sorte d'API émerger. @jkotas Je ne comprends toujours pas que vous soyez opposé à l'ajout d'une API basée sur des instances immuables ; vous avez d'abord dit que c'était parce que les copies 32 bits seraient lentes, ensuite parce que l'API mutable serait plus laconique (ce qui n'est pas vrai ; h.Combine(a).Combine(b)
(version immuable) est plus courte que h.Combine(a); h.Combine(b);
(mutable version)).
Cela dit, je suis prêt à revenir sur :
public static class HashCode
{
public static int Combine<T>(T value1, Tvalue2);
public static int Combine<T>(T value1, Tvalue2, IEqualityComparer<T> comparer);
public static int Combine<T>(T value1, Tvalue2, T value3);
public static int Combine<T>(T value1, Tvalue2, T value3, IEqualityComparer<T> comparer);
public static int Combine<T>(T value1, Tvalue2, T value3, T value4);
public static int Combine<T>(T value1, Tvalue2, T value3, T value4, IEqualityComparer<T> comparer);
// ... All the way until value8
}
Cela vous semble-t-il raisonnable ?
Je ne peux pas éditer mon message pour le moment, mais je viens de réaliser que toutes les méthodes ne peuvent pas accepter T. Dans ce cas, nous pouvons simplement avoir 8 surcharges acceptant tous les entiers et obliger l'utilisateur à appeler GetHashCode.
Si ces deux cas sont importants (ce que je suis prêt à accepter), alors pourquoi ne pas simplement donner deux API ? Documentez-les. Expliquez-leur clairement à quoi ils servent. Si les gens les utilisent correctement, tant mieux. Si les gens ne les utilisent pas correctement, c'est toujours bien. Après tout, ils ne font probablement pas les choses correctement aujourd'hui de toute façon, alors comment les choses sont-elles pires ?
Parce que les gens n'utilisent pas les choses correctement quand ils sont là. Prenons un exemple simple, XSS. Dès le début, même les formulaires Web avaient la possibilité d'encoder la sortie HTML. Cependant, les développeurs ne connaissaient pas le risque, ne savaient pas comment le faire correctement et n'ont découvert que trop tard, leur application a été publiée, et oups, maintenant leur cookie d'authentification a été levé.
Donner aux gens un choix de sécurité suppose qu'ils
Ces hypothèses ne sont généralement pas valables pour la majorité des développeurs, ils ne découvrent le problème que lorsqu'il est trop tard. Les développeurs ne vont pas aux conférences sur la sécurité, ils ne lisent pas les livres blancs et ils ne comprennent pas les solutions. Ainsi, dans le scénario ASP.NET HashDoS, nous avons fait le choix pour eux, nous les avons protégés par défaut, car c'était la bonne chose à faire et cela a eu le plus grand impact. Cependant, nous ne l'avons appliqué qu'aux chaînes, et cela a laissé les personnes qui construisaient des classes personnalisées à partir des entrées de l'utilisateur dans une mauvaise position. Nous devons faire ce qu'il faut, et aider à protéger ces clients maintenant, et en faire la valeur par défaut, avoir une fosse de succès, pas d'échec. La conception d'API pour la sécurité n'est parfois pas une question de choix, mais d'aide à l'utilisateur, qu'il le sache ou non.
Un utilisateur peut toujours créer un hachage non axé sur la sécurité ; donc étant donné les deux options
Alors la seconde est probablement meilleure ; et ce qui est suggéré n'aurait pas l'impact perf d'un hachage crypto complet ; donc c'est un bon compromis ?
L'une des questions récurrentes dans ces discussions a été de savoir quel algorithme est parfait pour tout le monde. Je pense qu'il est sûr de dire qu'il n'y a pas un seul algorithme parfait. Cependant, je ne pense pas que cela devrait nous empêcher de fournir quelque chose de mieux que du code comme ce que @CyrusNajmabadi a montré, qui a tendance à avoir une mauvaise entropie pour les entrées .NET courantes ainsi que d'autres bogues de hachage courants (comme perdre des données d'entrée ou être facilement réinitialisable).
J'aimerais proposer quelques options pour contourner le problème du "meilleur algorithme":
Marvin32.Create();
, il permet aux utilisateurs expérimentés de savoir ce qu'il a décidé de faire et ils peuvent facilement le changer pour un autre algorithme de la suite s'ils le souhaitent.CC @bartonjs , @terrajobst
@morganbr Il n'y a pas un seul algorithme parfait, mais je pense qu'avoir un algorithme, qui fonctionne assez bien la plupart du temps, exposé à l'aide d'une API simple et facile à comprendre est la chose la plus utile qui puisse être faite. Avoir une suite d'algorithmes en plus de cela, pour des utilisations avancées, c'est bien. Mais cela ne devrait pas être la seule option, je ne devrais pas avoir à apprendre qui est Marvin juste pour pouvoir mettre mes objets dans un Dictionary
.
Je ne devrais pas avoir à apprendre qui est Marvin juste pour pouvoir mettre mes objets dans un dictionnaire.
J'aime la façon dont tu dis ça. J'aime aussi que vous ayez mentionné le dictionnaire lui-même. IDictionary est quelque chose qui peut avoir des tonnes d'impls différents avec toutes sortes de qualités différentes (voir les API de collections sur de nombreuses plateformes). Cependant, nous ne fournissons toujours qu'un « Dictionnaire » de base qui fait un travail décent dans l'ensemble, même s'il n'excelle pas dans toutes les catégories.
Je pense que c'est ce que beaucoup de gens recherchent dans une bibliothèque de hachage. Quelque chose qui fait le travail, même s'il n'est pas parfait pour tous les usages.
@morganbr Je pense que les gens veulent simplement un moyen d'écrire GetHashCode qui soit meilleur que ce qu'ils font aujourd'hui (généralement une combinaison d'opérations mathématiques qu'ils ont copiées à partir de quelque chose sur le Web). Si vous pouvez simplement fournir un impl de base qui fonctionne bien, alors les gens seront heureux. Vous pouvez alors disposer d'une API en coulisse pour les utilisateurs avancés s'ils ont un besoin important de fonctions de hachage spécifiques .
En d'autres termes, les personnes qui écrivent des hashcodes aujourd'hui ne sauront pas ou ne se soucieront pas de savoir pourquoi elles voudraient Spooky vs Marvin vs Murmur. Seule une personne qui a un besoin particulier pour l'un de ces codes de hachage spécifiques irait chercher. Mais beaucoup de gens ont besoin de dire "voici l'état de mon objet, fournissez-moi un moyen de produire un hachage bien distribué qui est rapide que je peux ensuite utiliser avec des dictionnaires, et qui, je suppose, m'empêche d'être DOS si j'arrive pour prendre des entrées non fiables, les hacher et les stocker".
@CyrusNajmabadi Le problème est que si nous étendons nos notions actuelles de compatibilité dans le futur, nous constatons qu'une fois ce type expédié, il ne peut plus jamais changer (à moins que nous ne constations que l'algorithme est horriblement cassé de manière "il rend toutes les applications attaquables" ).
Une fois peut affirmer que si cela commence de manière stable et aléatoire, il devient facile de modifier l'implémentation, car vous ne pouvez de toute façon pas dépendre de la valeur d'une exécution à l'autre. Mais si quelques années plus tard, nous découvrons qu'il existe un algorithme qui fournit un équilibrage aussi bon sinon meilleur des seaux de hachage avec de meilleures performances dans le cas général, mais crée une structure impliquant une liste\ Selon la suggestion de Morgan, le code que vous écrivez aujourd'hui aura effectivement les mêmes caractéristiques de performance pour toujours. Pour les applications qui auraient pu s'améliorer, c'est dommage. Pour les applications qui auraient empiré, c'est fantastique. Mais lorsque nous trouvons le nouvel algorithme, nous l'enregistrons et nous modifions Roslyn (et suggérons un changement pour ReSharper/etc) pour commencer à générer des choses avec NewAwesomeThing2019 au lieu de SomeThingThatWasConsidedAwesomeIn2018. Tout ce qui est super boîte noire comme celui-ci ne peut être fait qu'une seule fois. Et puis nous sommes coincés avec ça pour toujours. Ensuite, quelqu'un écrit le suivant, qui a de meilleures performances moyennes, il y a donc deux implémentations de boîte noire dont vous ne savez pas pourquoi vous choisiriez entre elles. Et puis... et puis... . Donc, bien sûr, vous ne savez peut-être pas pourquoi Roslyn/ReSharper/etc a écrit automatiquement GetHashCode pour vous en utilisant Marvin32, ou Murmur, ou FastHash, ou une combinaison/conditionnelle basée sur IntPtr.Size. Mais vous avez le pouvoir de l'examiner. Et vous avez le pouvoir de le changer sur vos types plus tard, au fur et à mesure que de nouvelles informations sont révélées... mais nous vous avons également donné le pouvoir de le garder inchangé. (Ce serait triste si nous écrivions ceci, et dans 3 ans Roslyn/ReSharper/etc évitent explicitement de l'appeler, parce que le nouvel algorithme est tellement meilleur... Habituellement).
@bartonjs Qu'est-ce qui rend le hachage différent de tous les endroits où .Net vous fournit un algorithme de boîte noire ou une structure de données ? Par exemple, le tri (introsort), Dictionary
(chaînage séparé basé sur un tableau), StringBuilder
(liste chaînée de 8k morceaux), la plupart de LINQ.
Nous avons examiné cela de plus près aujourd'hui. Toutes nos excuses pour le retard et les allers-retours sur cette question.
```C#
// vivra dans l'assemblage de base
// .NET Framework : mscorlib
// .NET Core : System.Runtime / System.Private.CoreLib
système d'espace de noms
{
structure publique HashCode
{
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public void Add<T>(T value);
public void Add<T>(T value, IEqualityComparer<T> comparer);
public void Add<T>(T[] value);
public void Add<T>(T[] value, int index, int length);
public void Add(byte[] value);
public void Add(byte[] value, int index, int length);
public void Add(string value);
public void Add(string value, StringComparison comparisonType);
public int ToHashCode();
}
}
Notes:
* We decided to not override `GetHashCode()` to produce the hash code as this would be weird, both naming-wise as well as from a behavioral standpoint (`GetHashCode()` should return the object's hash code, not the one being computed).
* We decided to use `Add` for the builder patter and `Combine` for the static construction
* We decided to use not provide a static initialization method. Instead, `Add` will do this on first use.
* The struct is mutable, which is unfortunate but we feel the best compromise between making `GetHashCode()` very cheap & not cause any allocations while allowing the structure to be bigger than 32-bit so that the hash code algorithm can use more bits during accumulation.
* `Combine` will just call `<value>.GetHashCode()`, so it has the behavior of the value's type `GetHashCode()` implementation
- For strings that means different casing will produce different hash codes
- For arrays, that means the hash code doesn't look at the contents but uses reference semantics for the hash code
- If that behavior is undesired, the developer needs to use the builder-style approach
### Usage
The simple case is when someone just wants to produce a good hash code for a given type, like so:
```C#
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public override int GetHashCode() => HashCode.Combine(Id, FirstName, LastName);
}
Le cas le plus compliqué est celui où le développeur doit modifier la façon dont le hachage est calculé. L'idée est que le site d'appel passe le hachage souhaité plutôt que l'objet/la valeur, comme ceci :
```C#
classe partielle publique Client
{
public override int GetHashCode() =>
HashCode.Combine(
Identifiant,
StringComparer.OrdinalIgnoreCase.GetHashCode(FirstName),
StringComparer.OrdinalIgnoreCase.GetHashCode(Nom),
);
}
And lastly, if the developer needs more flexibility, such as producing a hash code for more than eight values, we also provide a builder-style approach:
```C#
public partial class Customer
{
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(Id);
hashCode.Add(FirstName, StringComparison.OrdinalIgnoreCase);
hashCode.Add(LastName, StringComparison.OrdinalIgnoreCase);
return hashCode.ToHashCode();
}
}
Cette question restera d'actualité. Afin de mettre en œuvre l'API, nous devons décider quel algorithme utiliser.
@morganbr fera une proposition pour les bons candidats. De manière générale, nous ne voulons pas écrire un algorithme de hachage à partir de zéro - nous voulons en utiliser un bien connu dont les propriétés sont bien comprises.
Cependant, nous devons mesurer l'implémentation pour les charges de travail .NET typiques et voir quel algorithme produit de bons résultats (débit et distribution). Il est probable que les réponses diffèrent selon l'architecture du processeur, nous devrions donc en tenir compte lors de la mesure.
@jamesqo , êtes-vous toujours intéressé à travailler dans ce domaine ? Dans ce cas, veuillez mettre à jour la proposition en conséquence.
@terrajobst , nous pourrions également vouloir public static int Combine<T1>(T1 value);
. Je sais que cela a l'air un peu drôle, mais cela fournirait un moyen de diffuser des bits de quelque chose avec un espace de hachage d'entrée limité. Par exemple, de nombreuses énumérations n'ont que quelques hachages possibles, n'utilisant que les derniers bits du code. Certaines collections sont construites sur l'hypothèse que les hachages sont répartis sur un plus grand espace, donc la diffusion des bits peut aider la collection à fonctionner plus efficacement.
public void Add(string value, StrinComparison comparison);
Nit : Le paramètre StringComparison
doit être nommé comparisonType
pour correspondre au nom utilisé partout ailleurs. StringComparison
est utilisé comme paramètre.
Les critères qui nous aideraient à choisir les algorithmes seraient :
Ce que nous aimerions vraiment voir, ce sont des chiffres de performance pour les candidats écrits en C# afin que nous puissions être raisonnablement confiants que leurs caractéristiques tiendront pour .NET. Si vous écrivez un candidat et que nous ne le choisissons pas pour cela, ce sera toujours un travail utile chaque fois que je réunirai la proposition d'API pour l'API de hachage non cryptographique.
Voici quelques candidats qui méritent d'être évalués (mais n'hésitez pas à en proposer d'autres) :
Dommage que les méthodes Add
ne puissent pas avoir un type de retour de ref HashCode
et retourner ref this
afin qu'elles puissent être utilisées de manière fluide,
Les retours de readonly ref
permettraient-ils ? /cc @jaredpar @VSadov
AVERTISSEMENT : si quelqu'un choisit une implémentation de hachage à partir d'une base de code existante quelque part sur Internet, veuillez conserver le lien vers la source et vérifier la licence (nous devrons également le faire).
Si la licence n'est pas compatible, nous devrons peut-être écrire l'algorithme à partir de zéro.
OMI, l'utilisation des méthodes Add devrait être extrêmement rare. Ce sera pour des scénarios très avancés, et le besoin de pouvoir être « fluide » ne sera pas vraiment là.
Pour les cas d'utilisation courants pour 99% de tous les cas de code utilisateur, on devrait pouvoir utiliser => HashCode.Combine(...)
et tout ira bien.
@morganbr
nous pourrions également vouloir
public static int Combine<T1>(T1 value);
. Je sais que cela a l'air un peu drôle, mais cela fournirait un moyen de diffuser des bits de quelque chose avec un espace de hachage d'entrée limité
Ayez du sens. Je l'ai ajouté.
@justinvp
Nit : Le paramètre
StringComparison
doit être nommécomparisonType
pour correspondre au nom utilisé partout ailleurs.StringComparison
est utilisé comme paramètre.
Fixé.
@CyrusNajmabadi
OMI, l'utilisation des méthodes
Add
devrait être extrêmement rare. Ce sera pour des scénarios très avancés, et le besoin de pouvoir être « fluide » ne sera pas vraiment là.
D'accord.
@benaadams - re: ref retournant this
de Add
- non, this
ne peut pas être retourné par ref dans les méthodes struct car il peut s'agir d'un rValue ou d'un temp.
```C#
ref var r = (nouveau T()).ReturnsRefThis();
// r fait ici référence à une variable. Lequel? Quelle est la portée/durée de vie ?
r = Autre chose();
```
Je m'interroge sur les collections :
@terrajobst
c# public void Add<T>(T[] value);
Pourquoi y a-t-il une surcharge pour les tableaux, mais pas pour les collections générales (c'est- IEnumerable<T>
dire
De plus, ne va-t-il pas être déroutant que HashCode.Combine(array)
et hashCode.Add((object)array)
se comportent dans un sens (utilisez l'égalité de référence) et que hashCode.Add(array)
se comportent d'une autre manière (combine les codes de hachage des valeurs dans le tableau) ?
@CyrusNajmabadi
Pour les cas d'utilisation courants pour 99% de tous les cas de code utilisateur, on devrait pouvoir simplement utiliser
=> HashCode.Combine(...)
et tout ira bien.
Si l'objectif est vraiment de pouvoir utiliser Combine
dans 99% des cas d'utilisation (et non, disons, 80%), alors Combine
ne devrait-il pas prendre en charge les collections de hachage basées sur les valeurs dans la collection ? Peut-être qu'il devrait y avoir une méthode distincte qui fait cela (soit une méthode d'extension, soit une méthode statique sur HashCode
) ?
Si Add est un scénario de puissance, devons-nous supposer que l'utilisateur doit choisir entre Object.GetHashCode et combiner des éléments individuels de collections ? Si cela pouvait aider, nous pourrions envisager de renommer les versions du tableau (et les versions potentielles de IEnumerable). Quelque chose comme:
c#
public void AddEnumerableHashes<T>(IEnumerable<T> enumerable);
public void AddEnumerableHashes<T>(T[] array);
public void AddEnumerableHashes<T>(T[] array, int index, int length);
Je me demande si nous aurions également besoin de surcharges avec IEqualityComparers.
Proposition : Faire en sorte que la structure du générateur implémente IEnumerable
pour prendre en charge la syntaxe de l'initialiseur de collection :
C#
return new HashCode {
SomeField,
OtherField,
{ SomeString, StringComparer.UTF8 },
{ SomeHashSet, HashSet<int>.CreateSetComparer() }
}.GetHashCode()
C'est beaucoup plus élégant que d'appeler Add()
à la main (en particulier, vous n'avez pas besoin d'une variable temporaire), et n'a toujours pas d'allocations.
@SLaks Peut-être que cette syntaxe plus agréable pourrait attendre https://github.com/dotnet/csharplang/issues/455 (en supposant que cette proposition ait un support), afin que HashCode
n'ait pas à implémenter de faux IEnumerable
?
Nous avons décidé de ne pas surcharger GetHashCode() pour produire le code de hachage car cela serait étrange, tant du point de vue du nom que du point de vue comportemental (GetHashCode() devrait renvoyer le code de hachage de l'objet, pas celui en cours de calcul).
Je trouve étrange que GetHashCode
ne retourne pas le hashcode calculé. Je pense que cela va embrouiller les développeurs. Par exemple, @SLaks l'a déjà utilisé dans sa proposition au lieu d'utiliser ToHashCode
.
@justinvp Si GetHashCode()
ne retourne pas le code de hachage calculé, il devrait probablement être marqué [Obsolete]
et [EditorBrowsable(Never)]
.
D'un autre côté, je ne vois pas l'inconvénient de renvoyer le code de hachage calculé.
@terrajobst
Nous avons décidé de ne pas surcharger
GetHashCode()
pour produire le code de hachage car ce serait étrange, tant du point de vue du nom que du point de vue comportemental (GetHashCode()
devrait renvoyer le code de hachage de l'objet, pas celui en cours de calcul).
Oui, GetHashCode()
doit renvoyer le code de hachage de l'objet, mais y a-t-il une raison pour laquelle les deux codes de hachage doivent être différents ? C'est toujours correct, puisque deux instances de HashCode
avec le même état interne renverront la même valeur à partir de GetHashCode()
.
@terrajobst je viens de voir votre commentaire. Excusez-moi pour la réponse tardive, j'ai mis du temps à examiner la notification car je pensais que ce ne serait que plus de va-et-vient qui n'allait nulle part. Content de voir que ce n'est pas le cas ! :tada:
Je serais ravi de prendre cela et de mesurer le débit/la distribution (je suppose que c'est ce que vous vouliez dire par "intéressé à travailler dans ce domaine"). Donnez-moi une seconde pour finir de lire tous les commentaires ici, cependant.
@terrajobst
Pouvons-nous changer
public void Add<T>(T[] value);
public void Add<T>(T[] value, int index, int length);
public void Add(byte[] value);
public void Add(byte[] value, int index, int length);
à
public void AddRange<T>(T[] values);
public void AddRange<T>(T[] values, int index, int count);
public void AddRange<T>(T[] values, int index, int count, IEqualityComparer<T> comparer);
? J'ai renommé Add
-> AddRange
pour éviter le comportement mentionné par @svick . J'ai supprimé les surcharges byte
car nous pouvons nous spécialiser en utilisant typeof(T) == typeof(byte)
à l'intérieur de la méthode si nous devons faire quelque chose de spécifique à un octet. Aussi, j'ai changé value
-> values
et length
-> count
. Il est également logique d'avoir une surcharge de comparateur.
@terrajobst Pouvez-vous me rappeler pourquoi
public void Add(string value);
public void Add(string value, StringComparison comparisonType);
est nécessaire quand on a
public void Add<T>(T value);
public void Add<T>(T value, IEqualityComparer<T> comparer);
?
@svick
@justinvp Si GetHashCode() ne retourne pas le code de hachage calculé, il devrait probablement être marqué [Obsolete] et [EditorBrowsable(Never)].
:+1:
@terrajobst Pouvons-nous revenir à une conversion implicite de edit : HashCode
-> int
, donc pas de méthode ToHashCode
?ToHashCode
c'est bien. Voir la réponse de @CyrusNajmabadi ci-dessous.
@jamesqo StringComparison
est une énumération.
Cependant, les gens pourraient utiliser l'équivalent StringComparer
place.
Pouvons-nous revenir à une conversion implicite de HashCode -> int, donc pas de méthode ToHashCode ?
Nous en avons discuté et avons décidé de ne pas le faire lors de la réunion. Le problème est que lorsque l'utilisateur obtient le « int » final, ce travail supplémentaire est souvent effectué. c'est-à-dire que les composants internes du hashcode effectueront souvent une étape de finalisation et peuvent se réinitialiser à un nouvel état. Que cela se produise avec une conversion implicite serait étrange. Si vous avez fait ceci :
HashCode hc = ...
int i1 = hc;
int i2 = hc;
Ensuite, vous pourriez obtenir des résultats différents.
Pour cette raison, nous n'aimons pas non plus la conversion explicite (car les gens ne considèrent pas les conversions comme un changement d'état interne).
Avec une méthode, nous pouvons documenter explicitement que cela se produit. On peut même potentiellement le nommer pour le véhiculer autant. c'est-à-dire "ToHashCodeAndReset" (bien que nous ayons décidé de ne pas le faire). Mais au moins la méthode peut avoir une documentation claire sur elle que l'utilisateur peut voir dans des choses comme intellisense. Ce n'est pas vraiment le cas avec les conversions.
J'ai supprimé les surcharges d'octets car nous pouvons nous spécialiser en utilisant typeof(T) == typeof(byte)
L'IIRC craignait que cela ne soit pas correct du point de vue de l'ECE. Mais cela n'a peut-être été que pour les cas "typeof()" sans valeur. Tant que le jit fera effectivement ce qu'il faut pour les cas typeof() de type valeur, alors cela devrait être bon.
@CyrusNajmabadi Je int
pouvait impliquer un état de mutation. ToHashCode
c'est alors.
Pour ceux qui pensent à la perspective crypto - http://tuprints.ulb.tu-darmstadt.de/2094/1/thesis.lehmann.pdf
@terrajobst , avez-vous eu le temps de lire mes commentaires (à partir d' ici ) et de décider si vous approuvez la forme de l'API modifiée ? Si c'est le cas, je pense que cela peut être marqué comme approuvé par l'API/à gagner et nous pouvons commencer à décider d'un algorithme de hachage.
@blowdart , une partie en particulier que vous voudriez souligner ?
Je n'ai peut-être pas été trop explicite à ce sujet ci-dessus, mais les seuls hachages non cryptographiques dans lesquels je ne connais pas les ruptures HashDoS sont Marvin et SipHash. C'est-à-dire que même l'ensemencement (disons) Murmur avec une valeur aléatoire peut toujours être rompu et utilisé pour un DoS.
Aucun, je viens de le trouver intéressant, et je pense que la documentation à ce sujet devrait dire "Ne pas utiliser sur les codes de hachage générés via des algorithmes cryptographiques".
Les décisions
AddRange
car le scénario n'est pas clair. Il est peu probable que les tableaux apparaissent très souvent. Et une fois que de plus grands tableaux sont impliqués, la question est de savoir si le calcul doit être mis en cache. Voir la boucle for du côté appelant montre clairement que vous devez y penser.IEnumerable
surcharges AddRange
car elles seraient allouées.Add
qui prend string
et StringComparison
. Oui, ceux-ci sont probablement plus efficaces que d'appeler via le IEqualityComparer
, mais nous pouvons résoudre ce problème plus tard.GetHashCode
comme obsolète avec erreur est une bonne idée, mais nous irions un peu plus loin et nous nous cacherions également d'IntelliSense.Cela nous laisse avec :
```C#
// vivra dans l'assemblage de base
// .NET Framework : mscorlib
// .NET Core : System.Runtime / System.Private.CoreLib
système d'espace de noms
{
structure publique HashCode
{
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public void Add<T>(T value);
public void Add<T>(T value, IEqualityComparer<T> comparer);
[Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)]
[EditorBrowsable(Never)]
public override int GetHashCode();
public int ToHashCode();
}
}
```
Prochaines étapes : le problème est à saisir -- pour implémenter l'API dont nous avons besoin avec plusieurs algorithmes candidats à titre d'expériences -- voir https://github.com/dotnet/corefx/issues/14354#issuecomment -305028686 pour la liste, afin que nous puissions décider quel algorithme utiliser (sur la base des mesures de débit et de distribution, réponse probablement différente selon l'architecture du processeur).
Complexité : Grande
Si quelqu'un est intéressé à le récupérer, veuillez nous contacter par ping. Il pourrait même y avoir de la place pour plusieurs personnes qui y travaillent ensemble. ( @jamesqo vous avez le choix prioritaire car vous avez investi le plus et le plus longtemps dans le problème)
@karelz Malgré mon commentaire ci - @morganbr et assez complexe , je ne peux donc pas la traduire facilement en C# pour tester par moi-même. J'ai peu d'expérience en C++, donc j'aurais aussi du mal à installer la bibliothèque et à écrire une application de test.
Cependant, je ne veux pas que cela reste sur la liste des candidats pour toujours. Si personne ne le prend dans une semaine à partir d'aujourd'hui, j'envisagerai de poster une question sur Programmers SE ou Reddit.
Je ne l'ai pas mis au banc (ni optimisé), mais voici une implémentation de base de l'algorithme de hachage Murmur3 que j'utilise dans plusieurs de mes projets personnels : https://gist.github.com/tannergooding/0a12559d1a912068b9aeb4b9586aad7f
Je pense que la solution la plus optimale ici va être de modifier dynamiquement l'algorithme de hachage en fonction de la taille des données d'entrée.
Ex : Mumur3 (et d'autres) sont très rapides pour les grands ensembles de données et offrent une excellente distribution, mais ils peuvent fonctionner « mal » (en termes de vitesse, pas de distribution) pour des ensembles de données plus petits.
J'imagine que nous devrions faire quelque chose comme : Si le nombre total d'octets est inférieur à X, faites l'algorithme A ; sinon, faites l'algorithme B. Ce sera toujours déterministe (par exécution), mais nous permettra de fournir une vitesse et une distribution en fonction de la taille réelle des données d'entrée.
Il convient également de noter que plusieurs des algorithmes mentionnés ont des implémentations spécialement conçues pour les instructions SIMD, donc une solution la plus performante impliquerait probablement un FCALL à un certain niveau (comme c'est le cas avec certaines des implémentations BufferCopy) ou peut impliquer de prendre une dépendance sur System.Numerics.Vector
.
@jamesqo , nous sommes heureux de vous aider à faire des choix ; ce dont nous avons le plus besoin d'aide, ce sont les données de performances pour les implémentations candidates (idéalement C#, bien que, comme le souligne @tannergooding , certains algorithmes nécessitent une prise en charge spéciale du compilateur). Comme je l'ai mentionné ci-dessus, si vous construisez un candidat qui n'est pas choisi, nous l'utiliserons probablement plus tard, alors ne vous inquiétez pas du gaspillage de travail.
Je sais qu'il existe des références pour diverses implémentations, mais je pense qu'il est important d'avoir une comparaison en utilisant cette API et une gamme probable d'entrées (par exemple, des structures avec 1 à 10 champs).
@tannergooding , ce type d'adaptabilité est peut-être le plus performant, mais je ne vois pas comment cela fonctionnerait avec la méthode Add car elle ne sait pas combien de fois elle sera appelée. Bien que nous puissions le faire avec Combine, cela signifierait qu'une série d'appels Add pourrait produire un résultat différent de l'appel Combine correspondant.
De plus, étant donné que la plage d'entrées la plus probable est de Combine`1
octets ( Combine`8
), j'espère qu'il n'y aura pas de grands changements de performances sur cette plage.
ce type d'adaptabilité est peut-être le plus performant, mais je ne vois pas comment cela fonctionnerait avec la méthode Add car elle ne sait pas combien de fois elle sera appelée.
Je ne suis pas personnellement convaincu que la forme de l'API soit tout à fait appropriée pour le hachage à usage général (elle est cependant proche) ...
Actuellement, nous exposons des méthodes Combine
pour la construction statique. Si ceux-ci sont destinés à combiner toutes les entrées et à produire un code de hachage finalisé, le nom est « pauvre » et quelque chose comme Compute
pourrait être plus approprié.
Si nous exposons des méthodes Combine
, elles devraient simplement mélanger toutes les entrées et les utilisateurs devraient être obligés d'appeler une méthode Finalize
qui prend la sortie de la dernière combinaison ainsi que le nombre total d'octets qui ont été combinés pour produire un code de hachage finalisé (la finalisation d'un code de hachage est importante car c'est ce qui provoque l'avalanche des bits).
Pour le modèle de générateur, nous exposons une méthode Add
et ToHashCode
. Il n'est pas clair si la méthode Add
est destinée à stocker les octets et à ne combiner/finaliser que sur l'appel à ToHashCode
(auquel cas nous pouvons choisir le bon algorithme dynamiquement) ou s'ils sont destiné à être combiné à la volée, il doit être clair que c'est le cas (et que l'implémentation doit suivre en interne la taille totale des octets combinés).
Pour ceux qui recherchent un point de départ moins compliqué, essayez xxHash32. C'est susceptible de se traduire assez facilement en C# (les gens l'ont fait ).
Je teste toujours localement, mais je vois les débits suivants pour mon implémentation C# de Murmur3.
Il s'agit des méthodes de combinaison statiques pour 1 à 8 entrées :
1070.18 mb/s
1511.49 mb/s
1674.89 mb/s
1957.65 mb/s
2083.24 mb/s
2140.94 mb/s
2190.27 mb/s
2245.53 mb/s
Mon implémentation suppose que GetHashCode
doit être appelé pour chaque entrée et que la valeur calculée doit être finalisée avant d'être renvoyée.
J'ai combiné les valeurs int
, car elles sont les plus simples à tester.
Pour calculer le débit, j'ai exécuté 10 001 itérations, en jetant la première itération en tant qu'exécution de "échauffement".
À chaque itération, j'exécute 10 000 sous-itérations où j'appelle HashCode.Combine
, en passant le résultat de la sous-itération précédente comme première valeur d'entrée de l'itération suivante.
Je fais ensuite la moyenne de toutes les itérations pour obtenir le temps écoulé moyen, puis je divise ce chiffre par le nombre de sous-itérations exécutées par boucle pour obtenir le temps moyen par appel. Je calcule ensuite le nombre d'appels pouvant être effectués par seconde et je le multiplie par le nombre d'octets combinés pour calculer le débit réel.
Va nettoyer le code et le partager un peu.
@tannergooding , cela semble être un grand progrès. Pour vous assurer que vous obtenez les bonnes mesures, l'objectif de l'API est qu'un appel à HashCode.Combine(a, b)
équivaut à appeler
HashCode hc = new HashCode();
hc.Add(a); // Initializes the hash state, calls a.GetHashCode() and feeds the result into the hash state
hc.Add(b); // Calls b.GetHashCode() and feeds the result into the hash state
return hc.ToHashCode(); // Finalizes the hash state, truncates it to an int, resets the internal state and returns the int
Dans les deux cas, les données doivent être introduites dans le même état de hachage interne et le hachage doit être finalisé une fois à la fin.
??
C'est effectivement ce que fait le code que j'ai écrit. La seule différence est que j'inline effectivement tout le code (il n'est pas nécessaire d'allouer new HashCode()
et de suivre le nombre d'octets combinés puisqu'il est constant).
@morganbr. Implémentation + Test de débit pour Murmur3 : https://gist.github.com/tannergooding/89bd72f05ab772bfe5ad3a03d6493650
MurmurHash3 est basé sur l'algorithme décrit ici : https://github.com/aappleby/smhasher/wiki/MurmurHash3 , le repo dit qu'il s'agit du MIT
Travailler sur xxHash32 (clause BSD-2 -- https://github.com/Cyan4973/xxHash/blob/dev/xxhash.c) et SpookyHash (domaine public -- http://www.burtleburtle.net/bob/hash /spooky.html) variantes
@tannergooding Encore une fois, ce n'est pas un expert en hachage mais je me suis souvenu [lire un article][1] qui disait que Murmur n'était pas résistant au DoS, donc il suffit de le souligner avant de choisir cela.
@jamesqo , je me trompe peut-être, mais je suis à peu près certain que cette vulnérabilité s'appliquait à Murmur2 et non à Murmur3.
Dans les deux cas, j'implémente plusieurs algorithmes afin que nous puissions obtenir des résultats de débit pour C#. La distribution et d'autres propriétés de ces algorithmes sont assez bien connues, nous pouvons donc choisir celui qui est le meilleur plus tard 😄
Oups, j'ai oublié de créer un lien vers l'article : http://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/.
@tannergooding OK. Ça a l'air correct :+1:
@tannergooding , j'ai jeté un coup d'œil à votre implémentation Murmur3 et elle semble généralement correcte et probablement assez bien optimisée. Pour être sûr de bien comprendre, utilisez-vous le fait que CombineValue et l'état interne de Murmur sont tous deux de 32 bits ? C'est probablement une très bonne optimisation pour ce cas et explique une partie de ma confusion précédente.
Si nous devions l'adopter, il faudrait peut-être quelques ajustements (ils ne feront probablement pas une grande différence pour les mesures de performances) :
En attendant, alors que j'aspire à cette API, à quel point est-ce mauvais pour moi d'implémenter GetHashCode via (field1, field2, field3).GetHashCode()
?
@jnm2 , le combineur de code de hachage ValueTuple a tendance à mettre vos entrées dans l'ordre dans le code de hachage (et à rejeter les moins récentes). Pour quelques champs et une table de hachage qui se divise par un nombre premier, vous ne le remarquerez peut-être pas. Pour beaucoup de champs ou une table de hachage qui divise par une puissance de deux, l'entropie du dernier champ que vous insérez aura le plus d'influence sur si vous avez des collisions (par exemple si votre dernier champ est un bool ou un petit int, vous 'aura probablement beaucoup de collisions, si c'est un guide, vous n'en aurez probablement pas).
ValueTuple ne fonctionne pas non plus bien avec les champs qui sont tous à 0.
En passant, j'ai dû arrêter de travailler sur d'autres implémentations (avoir un travail plus prioritaire). Je ne sais pas quand je pourrai le récupérer.
Donc, si ce n'est pas assez bon pour un type structuré, pourquoi est-ce assez bon pour un tuple ?
@jnm2 , c'est l'une des raisons pour lesquelles cette fonctionnalité mérite d'être construite - nous pouvons donc remplacer les hachages de qualité inférieure dans le cadre.
Grande table de fonctions de hachage avec des caractéristiques de performance et de qualité :
https://github.com/leo-yuriev/t1ha
@arespr Je pense que l'équipe recherche une implémentation C# des fonctions de hachage. Merci pour le partage, cependant.
@tannergooding Vous ne parvenez toujours pas à résoudre ce problème ? Si c'est le cas, je publierai sur Reddit/Twitter que nous recherchons un expert en hachage.
edit: A fait un post sur Reddit. https://www.reddit.com/r/csharp/comments/6qsysm/looking_for_hash_expert_to_help_net_core_team/?ref=share&ref_source=link
@jamesqo , j'ai quelques choses plus prioritaires dans mon assiette et je ne pourrai pas y arriver dans les 3 prochaines semaines.
De plus, les mesures actuelles seront limitées par ce que nous pouvons actuellement coder en C#, cependant, si/quand cela devient une chose (https://github.com/dotnet/designs/issues/13), les mesures changeront probablement quelque peu ;)
De plus, les mesures actuelles seront limitées par ce que nous pouvons actuellement coder en C#, cependant, si/quand cela deviendra une chose (dotnet/designs#13), les mesures changeront probablement quelque peu ;)
C'est OK - nous pouvons toujours changer l'algorithme de hachage une fois que les intrinsèques sont disponibles, encapsuler/randomiser le code de hachage nous permet de le faire. Nous recherchons simplement quelque chose qui offre le meilleur compromis performances/distribution pour le runtime dans son état actuel.
@jamesqo , merci d'avoir cherché des personnes pour vous aider. Nous serions heureux que quelqu'un qui n'est pas un expert en hachage travaille également sur ce sujet. Une fois que nous aurons choisi les candidats, nos experts feront ce que nous faisons en cas de changement : examiner le code pour en vérifier l'exactitude, les performances, la sécurité, etc.
Salut! Je viens de lire la discussion, et au moins pour moi, il semble que l'affaire soit fermement close en faveur du murmur3-32 PoC. Ce qui d'ailleurs me semble être un très bon choix, et je recommanderais de ne plus dépenser de travail inutile (mais peut-être même de laisser tomber les membres .Add()
...).
Mais dans le cas peu probable où quelqu'un voudrait continuer avec plus de travail de performance, je pourrais fournir du code pour xx32, xx64, hsip13/24, seahash, murmur3-x86/32 (et j'ai intégré l'impl marvin32 d'en haut), et (encore non optimisé) sip13/24, spookyv2. Certaines versions de City semblent assez faciles à porter, en cas de besoin. Ce projet à moitié abandonné avait un cas d'utilisation légèrement différent en tête, il n'y a donc pas de classe HashCode avec l'API proposée ; mais pour l'analyse comparative, cela ne devrait pas avoir beaucoup d'importance.
Certainement pas prêt pour la production : le code applique des quantités
Si cela peut vous aider, je devrais trouver suffisamment de temps au cours des deux prochaines semaines pour résoudre les problèmes les plus flagrants et rendre le code et quelques résultats préliminaires disponibles.
@gimpf
Je viens de lire la discussion, et au moins pour moi, il semble que l'affaire soit fermement close en faveur du murmur3-32 PoC. Quel BTW me semble être un très bon choix, et je recommanderais de ne plus dépenser de travail inutile
Non, les gens ne sont pas encore favorables à Murmur3. Nous voulons nous assurer que nous choisissons le meilleur algorithme absolu en termes d'équilibre entre performance/distribution, afin de ne rien négliger.
Mais dans le cas peu probable où quelqu'un voudrait continuer avec plus de travail de performance, je pourrais fournir du code pour xx32, xx64, hsip13/24, seahash, murmur3-x86/32 (et j'ai intégré l'impl marvin32 d'en haut), et (encore non optimisé) sip13/24, spookyv2. Certaines versions de City semblent assez faciles à porter, en cas de besoin.
Oui s'il vous plaît! Nous voulons rassembler du code pour autant d'algorithmes que possible à tester. Chaque nouvel algorithme auquel vous pouvez contribuer est précieux. Ce serait très apprécié si vous pouviez également porter les algorithmes de City.
Certainement pas prêt pour la production : le code applique des quantités généreuses de force brute comme copier-pâtes, étalement cancéreux d'agressif en ligne et dangereux ; l'endianess n'existe pas, pas plus que les lectures non alignées. Même les tests contre les vecteurs de test ref-impl sont par euphémisme "incomplets".
C'est bon. Apportez simplement le code et quelqu'un d'autre peut le trouver si le besoin s'en fait sentir.
Si cela peut vous aider, je devrais trouver suffisamment de temps au cours des deux prochaines semaines pour résoudre les problèmes les plus flagrants et rendre le code et quelques résultats préliminaires disponibles.
Oui ce serait super!
@jamesqo Ok, je laisserai une note une fois que j'aurai quelque chose à montrer.
@gimpf ça sonne vraiment bien et nous aimerions connaître vos progrès au fur et à mesure (pas besoin d'attendre de travailler sur chaque algorithme !). Ne pas être prêt pour la production est acceptable tant que vous pensez que le code produit des résultats corrects et que les performances sont une bonne représentation de ce que nous verrions dans une implémentation prête pour la production. Une fois que nous avons sélectionné les candidats, nous pouvons travailler avec vous pour obtenir des implémentations de haute qualité.
Je n'ai pas vu d'analyse sur la façon dont l'entropie de Seahash se compare à d'autres algorithmes. Avez-vous des indications là-dessus ? Il présente des compromis de performances intéressants... la vectorisation semble rapide, mais l'arithmétique modulaire semble lente.
@morganbr J'ai un teaser prêt.
A propos de SeaHash : Non, je ne connais pas encore la qualité ; au cas où la performance serait intéressante, je l'ajouterais à SMHasher. Au moins, l'auteur prétend que c'est bon (en l'utilisant pour des sommes de contrôle dans un système de fichiers), et prétend également qu'aucune entropie n'est rejetée pendant le mixage.
À propos des hachages et des benchmarks : Project Haschisch.Kastriert , page wiki avec les premiers résultats de benchmarking comparant xx32, xx64, hsip13, hsip24, marvin32, sea et murmur3-32.
Quelques mises en garde importantes :
Premières impressions:
HashSet<>
besoin de travail, car tout est presque à l'intérieur de l'erreur de mesure (j'ai vu des différences plus importantes, mais cela ne vaut toujours pas la peine d'en parler)Je vous écrirai à nouveau une fois que j'aurai un peu amélioré la situation.
@gimpf , c'est un début fantastique ! J'ai regardé le code et les résultats et j'ai quelques questions.
Vos résultats HashSet sont particulièrement intéressants. S'ils tiennent le coup, c'est un cas possible pour préférer une meilleure entropie à un temps de hachage plus rapide.
@morganbr Ce week-end était plus
A propos de vos questions :
- Vos résultats montrent que SimpleMultiplyAdd est environ 5 fois plus lent que Murmur3a de @tannergooding. Cela semble étrange...
Je me demandais. C'était une erreur de copier/coller, SimpleMultiplyAdd combinait toujours quatre valeurs... De plus, en réorganisant certaines déclarations, le combineur multiplier-ajouter est devenu légèrement plus rapide (~ 60 % de débit plus élevé).
Est-il possible que vos implémentations aient une inefficacité commune qui ne se trouve pas dans cette implémentation Murmur ou dois-je lire cela comme des implémentations personnalisées ayant un gros avantage par rapport à celles à usage général ?
Certaines choses me manquent probablement, mais il semble que les implémentations à usage général de .NET ne soient pas utilisables pour ce cas d'utilisation. J'ai écrit des méthodes de style Combine pour tous les algorithmes, et la combinaison de code de hachage par rapport à la plupart fonctionne _beaucoup_ mieux que celles à usage général.
Cependant, même ces implémentations restent trop lentes ; des travaux supplémentaires sont nécessaires. Les performances .NET dans ce domaine sont absolument opaques pour moi ; l'ajout ou la suppression d'une copie d'une variable locale peut facilement changer les performances d'un facteur deux. Je ne serai probablement pas en mesure de fournir des implémentations suffisamment bien optimisées dans le but de sélectionner la meilleure option.
- Avoir des résultats pour 1, 2 et 4 combinaisons est bien, mais cette API va jusqu'à 8.
J'ai étendu les références de la moissonneuse-batteuse. Pas de surprises de ce côté.
- J'ai vu que tu tournais sur X64 (...), est-ce que c'est facile pour toi d'avoir aussi des résultats X86 ?
C'était autrefois, mais j'ai ensuite porté sur .NET Standard. Maintenant, je suis dans l'enfer des dépendances, et seuls les benchmarks .NET Core 2 et CLR 64 bits fonctionnent. Cela peut être résolu assez facilement une fois que j'ai résolu les problèmes actuels.
Pensez-vous que cela le fera dans la version 2.1 ?
@gimpf Vous n'avez pas posté depuis un moment - avez-vous une mise à jour sur l'avancement de vos implémentations ? :souriant:
@jamesqo J'ai corrigé un benchmark qui provoquait des résultats étranges et j'ai ajouté City32, SpookyV2, Sip13 et Sip24 à la liste des algorithmes disponibles. Les Sips sont aussi rapides que prévu (par rapport au débit de xx64), City et Spooky ne le sont pas (c'est toujours le cas pour SeaHash).
Pour combiner des codes de hachage, Murmur3-32 ressemble toujours à un bon pari, mais je n'ai pas encore effectué de comparaison plus exhaustive.
Sur une autre note, l'API de streaming (.Add()) a pour effet secondaire malheureux de supprimer certains algorithmes de hachage de la liste des candidats. Étant donné que les performances d'une telle API sont également discutables, vous voudrez peut-être repenser à l'offrir dès le début.
Si la partie .Add()
était évitée, et étant donné que le combinateur de hachage utilise une graine, je ne pense pas qu'il y aurait de mal à nettoyer le combinateur de tg, à créer une petite suite de tests, et appelez ça un jour. Étant donné que je n'ai que quelques heures chaque week-end et que l'optimisation des performances est un peu fastidieuse, la version plaquée or pourrait s'éterniser un peu...
@gimpf , cela semble être un grand progrès. Avez-vous un tableau des résultats à portée de main afin que nous puissions voir s'il y en a assez pour prendre une décision et aller de l'avant ?
@morganbr J'ai mis à jour mes résultats d'analyse comparative .
Pour l'instant, je n'ai que des résultats 64 bits sur .NET Core 2. Pour cette plate-forme, City64 sans graine est le plus rapide dans toutes les tailles. Incorporant une graine, XX-32 est à égalité avec Murmur-3-32. Heureusement, ce sont les mêmes algorithmes qui ont la réputation d'être rapides pour les plates-formes 32 bits, mais nous devons évidemment vérifier que cela est également vrai pour mon implémentation. Les résultats semblent être représentatifs des performances du monde réel, sauf que Sea et SpookyV2 semblent inhabituellement lents.
Vous devrez déterminer dans quelle mesure vous avez réellement besoin d'une protection contre le hachage pour les combinateurs de code de hachage. Si l'ensemencement n'est nécessaire que pour rendre le hachage manifestement inutilisable pour la persistance, city64 une fois XOR avec une graine 32 bits serait une amélioration. Comme cet utilitaire n'est là que pour combiner des hachages (et ne pas remplacer par exemple le code de hachage pour les chaînes, ou être un hachage pour les tableaux d'entiers, etc.), cela pourrait suffire.
Si OTOH vous pensez en avoir besoin, vous serez heureux de voir que Sip13 est généralement moins de 50% plus lent que XX-32 (sur les plates-formes 64 bits), mais ce résultat sera probablement très différent pour les applications 32 bits.
Je ne sais pas à quel point c'est pertinent pour corefx, mais j'ai ajouté les résultats LegacyJit 32bit (w/FW 4.7).
Je voudrais dire que les résultats sont ridiculement lents. Cependant, à titre d'exemple, à 56 MiB/s contre 319 MiB/s, je ne rigole pas (c'est Sip, il manque le plus l'optimisation de rotation à gauche). Je pense que je me souviens pourquoi j'ai annulé mon projet d'algorithme de hachage .NET en janvier...
Ainsi, RyuJit-32bit est toujours manquant et donnera (espérons-le) des résultats très différents, mais pour LegacyJit-x86, Murmur-3-32 gagne facilement, et seuls City-32 et xx-32 peuvent s'en approcher. Murmur a toujours de mauvaises performances à seulement environ 0,4 à 1,1 Go/s au lieu de 0,6 à 2 Go/s (sur la même machine), mais au moins c'est dans la bonne fourchette.
Je vais exécuter les benchmarks sur quelques-unes de mes box ce soir et publier les résultats (Ryzen, i7, Xeon, A10, i7 Mobile et je pense quelques autres).
@tannergooding @morganbr Quelques mises à jour intéressantes et importantes.
Important avant tout :
Bonne choses:
Pour exécuter une suite sur toutes les implémentations principales pour combiner les codes de hachage, y compris "Empty" (overhead pur) et "multiply-add" (version optimisée pour la vitesse de la célèbre réponse SO) :
bin\Release\net47\Haschisch.Benchmarks.Net47.exe -j:clr_x86 -j:clr_x64_legacy -j:clr_x64 -j:core_x64 -- CombineHashCode --allcategories=prime
(_L'exécution de benchmarks Core 32 bits semble nécessiter une version préliminaire de BenchmarkDotNet (ou peut-être une configuration 32 bits uniquement plus l'utilisation du Bench-runner basé sur Core). Cela devrait alors fonctionner avec -j:core_x86, espérons-le)_
Résultats : Après toutes les corrections de bugs, xx32 semble l'emporter pour toutes les surcharges avec RyuJIT 64 bits, sous Windows 10 sur un mobile Haswell i7, en un run "rapide". Entre les Sips et marvin32, Sip-1-3 gagne toujours. Sip-1-3 est environ 4 fois plus lent que xx32, qui est à nouveau environ 2 fois plus lent qu'un combineur primitif de multiplication-addition. Les résultats du noyau 32 bits sont toujours manquants, mais j'attends plus ou moins une version stable de BenchmarkDotNet qui résoudra ce problème pour moi.
(Edit) Je viens d'ajouter une exécution rapide d'un benchmark pour accéder à un hash-set . Cela dépend évidemment beaucoup plus des détails que les repères µ ci-dessus, mais vous voudrez peut-être y jeter un coup d'œil.
Merci encore une fois @gimpf pour les données fantastiques ! Voyons si nous pouvons en faire une décision.
Pour commencer, je diviserais les algorithmes comme ceci :
Entropie rapide+bonne (classée par vitesse) :
Résistant au HashDoS :
Hors contention (lent) :
Hors contention (mauvaise entropie) :
Avant de choisir un gagnant, j'aimerais m'assurer que les autres personnes sont d'accord avec mon classement ci-dessus. Si cela tient, je pense que nous devons simplement choisir de payer 2x pour la résistance HashDoS, puis de passer par la vitesse.
@morganbr Votre regroupement semble Jean-Philippe Aumasson , qui a écrit sip-hash avec DJB. Après cette discussion, ils ont décidé d'opter pour sip-1-3 pour les tables de hachage.
(Voir PR rust:#33940 et le problème de rouille qui l'accompagne
Sur la base des données et des commentaires, je voudrais proposer que nous utilisions xxHash32 sur toutes les architectures. La prochaine étape consiste à le mettre en œuvre. @gimpf , êtes-vous intéressé à mettre en place un PR pour cela ?
Pour ceux qui sont préoccupés par HashDoS, je ferai bientôt un suivi avec une proposition d'API de hachage à usage général qui devrait inclure Marvin32 et pourrait inclure SipHash. Ce sera également un endroit approprié pour les autres implémentations sur lesquelles @gimpf et @tannergooding ont travaillé.
@morganbr Je peux mettre en place un PR si le temps le permet. De plus, personnellement, je préférerais aussi xx32, tant qu'il ne réduit pas l'acceptation.
@gimpf , à quoi ressemble ton temps ? Si vous n'avez pas vraiment le temps, nous pouvons également voir si quelqu'un d'autre aimerait tenter le coup.
@morganbr J'avais prévu de le faire jusqu'au 5 novembre, et il semble toujours bon que je trouve le temps dans les deux prochaines semaines.
@gimpf ,
@terrajobst - Je suis un peu en retard à la fête (désolé), mais ne pouvons-nous pas changer le type de retour de la méthode Add ?
```c#
public HashCode Ajouter
public HashCode Ajouter
The params code is clearly there for scenarios where you have multiple fields, e.g.
```c#
public override int GetHashCode() => new HashCode().Add(Name, Surname).ToHashCode();
Cependant, exactement la même chose peut être réalisée comme ceci, mais avec une allocation de tableau moins inutile :
c#
public override int GetHashCode() => new HashCode().Add(Name).Add(Surname).Add(Age).ToHashCode();
Notez que les types peuvent également être mélangés. Cela pourrait évidemment être fait en ne l' appelant pas couramment à l'intérieur d'une méthode régulière. Compte tenu de cet argument selon lequel l'interface fluide n'est pas absolument nécessaire, pourquoi la surcharge inutile de params
existe-t-elle pour commencer ? Si cette suggestion est une mauvaise suggestion, alors la surcharge de params
tombe sur le même axe. Cela, et forcer une méthode régulière pour un hashcode trivial mais optimal semble être beaucoup de cérémonie.
Edit : Un implicit operator int
serait aussi bien pour DRY, mais pas vraiment crucial.
@jcdickinson
ne pouvons-nous pas changer le type de retour de la méthode Add ?
Nous en avons déjà discuté dans l'ancienne proposition, et elle a été rejetée.
pourquoi la surcharge de paramètres inutiles existe-t-elle pour commencer ?
Nous n'ajoutons aucune surcharge de paramètres ? Faites un Ctrl+F pour "params" sur cette page Web, et vous verrez que votre commentaire est le seul endroit où ce mot apparaît.
Un opérateur implicite int serait également bien pour DRY, mais pas exactement crucial.
Je crois que cela a également été discuté quelque part ci-dessus ...
@jamesqo merci pour l'explication.
surcharges de paramètres
Je voulais dire AddRange
, mais je suppose qu'il n'y aura pas de traction là-dessus.
@jcdickinson AddRange
figurait dans la proposition d'origine, mais ce n'est pas dans la version actuelle. Il a été rejeté par l'examen de l'API (voir https://github.com/dotnet/corefx/issues/14354#issuecomment-308190321 par @terrajobst) :
Nous devrions supprimer toutes les méthodes
AddRange
car le scénario n'est pas clair. Il est peu probable que les tableaux s'affichent très souvent. Et une fois que de plus grands tableaux sont impliqués, la question est de savoir si le calcul doit être mis en cache. Voir la boucle for du côté appelant montre clairement que vous devez y penser.
@gimpf Je suis allé de l'avant et j'ai la proposition avec xxHash32 . N'hésitez pas à saisir cette implémentation. Il a des tests contre les vecteurs xxHash32 réels.
Concernant l'interface. Je suis pleinement conscient que je fais une montagne d'une taupinière - n'hésitez pas à l'ignorer. J'utilise la proposition actuelle contre de vrais trucs et c'est beaucoup de répétitions ennuyeuses.
J'ai joué avec l'interface et je comprends maintenant pourquoi l'interface fluide a été rejetée ; c'est nettement plus lent.
BenchmarkDotNet=v0.10.9, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i7-4800MQ CPU 2.70GHz (Haswell), ProcessorCount=8
Frequency=2630626 Hz, Resolution=380.1377 ns, Timer=TSC
.NET Core SDK=2.0.2
[Host] : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT
DefaultJob : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT
Utilisation d'une méthode non intégrée comme source de code de hachage ; 50 invocations de Add vs une méthode d'extension fluide :
| Méthode | Moyenne | Erreur | DevStd | à l'échelle |
|------- |---------:|---------:|---------:|-------: |
| Ajouter | 401,6 secondes | 1,262 ns | 1.180 ns | 1,00 |
| Décompte | 747.8 ns | 2.329 ns | 2,178 ns | 1,86 |
Cependant, le modèle suivant fonctionne :
```c#
structure publique HashCode : System.Collections.IEnumerable
{
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolète("Cette méthode est fournie pour la syntaxe de l'initialiseur de collection.", erreur : vrai)]
public IEnumerator GetEnumerator() => lancer une nouvelle NotImplementedException();
}
public override int GetHashCode() => new HashCode()
{
Age, // int
{ Name, StringComparer.Ordinal }, // use Comparer
Hat // some arbitrary object
}.ToHashCode();
```
Il présente également des caractéristiques de performances identiques à la proposition actuelle :
| Méthode | Moyenne | Erreur | DevStd | à l'échelle |
|------------ |---------:|---------:|---------:|--- ----:|
| Ajouter | 405,0 ns | 2.130 ns | 1,889 ns | 1,00 |
| Initialiseur | 400,8 ns | 4.821 ns | 4,274 ns | 0,99 |
Malheureusement, c'est un peu un hack, car le IEnumerable
doit être implémenté pour satisfaire le compilateur. Cela étant dit, le Obsolete
produira une erreur même sur foreach
- vous devrez vraiment vouloir casser les choses pour rencontrer l'exception. Le MSIL entre les deux est essentiellement identique.
@jcdickinson merci d'avoir saisi le problème. Je vous ai envoyé une invitation Collaborator, faites-moi savoir quand vous acceptez et je pourrai vous attribuer ce problème (en m'attribuant moi-même entre-temps).
Conseil de pro : une fois que vous avez accepté, GitHub vous inscrira automatiquement à toutes les notifications du dépôt (plus de 500 par jour). vous vous êtes abonné.
@jcdickinson , je suis définitivement intéressé par les moyens d'éviter les répétitions ennuyeuses (bien que je
var hc = new HashCode();
var newHc = hc.Add(foo);
hc.Add(bar);
return newHc.ToHashCode();
Étant donné que la proposition sur ce fil est déjà approuvée (et que vous êtes sur la bonne voie pour la fusionner), je vous suggère de commencer une nouvelle proposition d'API pour tout changement.
@karelz Je crois que @gimpf a déjà saisi ce problème à l'avance. Puisqu'il est plus familiarisé avec l'implémentation, veuillez plutôt attribuer ce problème à @gimpf . ( modifier : nvm)
@terrajobst Une sorte de requête API de dernière minute pour cela. Puisque nous avons marqué GetHashCode
obsolète, nous disons implicitement à l'utilisateur que les HashCode
ne sont pas des valeurs destinées à être comparées, bien qu'il s'agisse de structures généralement immuables/comparables. Dans ce cas, devrions-nous également marquer Equals
obsolète ?
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
[EditorBrowsable(Never)]
// If this is too harsh, base.Equals() is fine as long as the [Obsolete] stays
public override bool Equals(object obj) => throw new NotSupportedException("HashCode is a mutable struct and should not be compared with other HashCodes.");
Je pense que quelque chose de similaire a été fait avec Span
.
Si c'est accepté, alors je pense...
should not
, ou may not
au lieu de cannot
dans le message Obsolète.@ Joe4evr Bien avec moi; J'ai mis à jour le commentaire. Il peut également être avantageux d'inclure le même message dans l'exception GetHashCode
, puis :
public override int GetHashCode() => throw new NotSupportedException("HashCode is a mutable struct and should not be compared with other HashCodes.");
@morganbr Pourquoi avez-vous rouvert ça ?
Le PR pour l'exposer dans CoreFX n'est pas encore passé.
@gimpf avez-vous le code que vous avez évalué et/ou seriez-vous en mesure de voir rapidement comment le paquet SpookilySharp nuget fonctionne. Je cherche à dépoussiérer ce projet après quelques années de stagnation et je suis curieux de voir comment il tient.
@JonHanna Il l'a posté ici : https://github.com/gimpf/Haschisch.Kastriert
@JonHanna , je serais intéressé de savoir comment se
@morganbr Où serait un forum approprié pour discuter d'une telle API ? Je m'attends à ce qu'une telle API ne se limite pas au plus petit dénominateur commun, et peut-être qu'une bonne API aura également besoin d'un JIT amélioré par rapport à la gestion des structures plus grandes. Discuter de tout ce qui pourrait être mieux fait dans un numéro séparé...
@gimpf En a ouvert un pour vous. dotnet/corefx#25666
@morganbr - Pouvons-nous obtenir le nom et la version du package qui incluront ce commit ?
@karelz , pourriez-vous aider @smitpatel avec les informations sur le package/la version ?
J'essaierais de construire quotidiennement .NET Core - j'attendrais jusqu'à demain.
Je ne pense pas qu'il existe un package sur lequel vous pouvez simplement prendre une dépendance.
Question aux participants ici. L'IDE Roslyn permet aux utilisateurs de générer une impl GetHashCode basée sur un ensemble de champs/propriétés dans leur class/struct . Idéalement, les gens pourraient utiliser le nouveau HashCode.Combine qui a été ajouté dans https://github.com/dotnet/corefx/pull/25013 . Cependant, certains utilisateurs n'auront pas accès à ce code. Nous aimerions donc pouvoir toujours générer un GetHashCode qui fonctionnera pour eux.
Récemment, il est venu à notre attention que le formulaire que nous générons est problématique. À savoir, parce que VB compile avec des contrôles de débordement activés par défaut, et notre impl provoquera des débordements. De plus, VB n'a aucun moyen de désactiver les contrôles de débordement pour une région de code. Il est activé ou désactivé pour l'ensemble de l'assemblage.
Pour cette raison, j'aimerais pouvoir remplacer l'impl que nous fournissons par un formulaire qui ne souffre pas de ces problèmes. Idéalement, le formulaire généré aurait les propriétés suivantes :
a + b + c + d
ou a ^ b ^ c ^ d
.Par exemple, une option pour VB serait de générer quelque chose comme :
return (a, b, c, d).GetHashCode()
Mais cela dépend alors de la référence à System.ValueTuple. Idéalement, nous pourrions avoir un impl qui fonctionne même en l'absence de cela.
Est-ce que quelqu'un connaît un algorithme de hachage décent qui peut fonctionner avec ces contraintes ? Merci!
--
Remarque : notre code émis existant est :
Dim hashCode = -252780983
hashCode = hashCode * -1521134295 + i.GetHashCode()
hashCode = hashCode * -1521134295 + j.GetHashCode()
Return hashCode
Cela peut clairement déborder.
Ce n'est pas non plus un problème pour C# car nous pouvons simplement ajouter unchecked { }
autour de ce code. Ce contrôle fin n'est pas possible dans VB.
Est-ce que quelqu'un connaît un algorithme de hachage décent qui peut fonctionner avec ces contraintes ? Merci!
Eh bien, vous pourriez faire Tuple.Create(...).GetHashCode()
. Évidemment, cela implique des allocations, mais cela semble mieux que de lever une exception.
Y a-t-il une raison pour laquelle vous ne pouvez pas simplement dire à l'utilisateur d'installer System.ValueTuple
? Puisqu'il s'agit d'une fonctionnalité de langage intégrée, je suis sûr que le package System.ValueTuple est très compatible avec pratiquement toutes les plates-formes, n'est-ce pas ?
Évidemment, cela implique des allocations, mais cela semble mieux que de lever une exception.
Oui. ce serait bien que cela ne cause pas d'allocations.
Y a-t-il une raison pour laquelle vous ne pouvez pas simplement dire à l'utilisateur d'installer System.ValueTuple ?
Ce serait le comportement si nous générons l'approche ValueTuple. Cependant, encore une fois, ce serait bien si nous pouvions simplement générer quelque chose de bien qui correspond à la façon dont l'utilisateur a actuellement structuré son code, sans les obliger à modifier leur structure de manière lourde.
Il semble vraiment que les utilisateurs de VB devraient avoir un moyen de résoudre ce problème de manière raisonnable :) Mais une telle approche m'échappe :)
@CyrusNajmabadi , Si vous avez vraiment besoin de faire votre propre calcul de hachage dans le code de l'utilisateur, CRC32 pourrait fonctionner car il s'agit d'une combinaison de recherches de table et de XOR (mais pas d'arithmétique qui peut déborder). Il y a quand même quelques inconvénients :
Si vous ne le faites pas déjà, j'espère que vous pourrez détecter le type HashCode et l'utiliser lorsque cela est possible, car XXHash devrait être bien meilleur.
@morganbr Voir https://github.com/dotnet/roslyn/pull/24161
Nous procédons comme suit :
Return (a, b, c, ...).GetHashCode()
C'est '3d' c'est vraiment dommage. Fondamentalement, quelqu'un utilisant VB mais n'utilisant pas ValueTuple ou un système récent, ne pourra pas nous utiliser pour obtenir un algorithme de hachage raisonnable généré pour eux.
Vous auriez besoin de mettre une table de recherche de 256 entrées quelque part dans le code
Ce serait complètement désagréable :)
Le code de génération de table est-il également désagréable ? Au moins en suivant l'exemple de Wikipédia , ce n'est pas beaucoup de code (mais il doit quand même aller quelque part dans la source de l'utilisateur).
À quel point serait-il horrible d'ajouter la source HashCode au projet comme le fait Roslyn (avec IL) avec des définitions de classe d'attributs de compilateur (beaucoup plus simples) alors qu'elles ne sont disponibles via aucun assembly référencé ?
À quel point serait-il horrible d'ajouter la source HashCode au projet comme le fait Roslyn avec les définitions de classe d'attributs de compilateur (beaucoup plus simples) alors qu'elles ne sont disponibles via aucun assembly référencé ?
Je suis juste surpris qu'il n'y ait aucun bon moyen de faire fonctionner les mathématiques de débordement dans VB :(
Donc, au minimum, même si nous hachions deux valeurs ensemble, il semble que nous devions créer :
```c#
var hc1 = (uint)(value1?.GetHashCode() ?? 0); // peut déborder
var hc2 = (uint)(value2?.GetHashCode() ?? 0); // peut déborder
uint hash = MixEmptyState();
hash += 8; // can overflow
hash = QueueRound(hash, hc1);
hash = QueueRound(hash, hc2);
hash = MixFinal(hash);
return (int)hash; // can overflow
Note that this code already has 4 lines that can overflow. It also has two helper functions you need to call (i'm ignoring MixEmptyState as that seems more like a constant). MixFinal can *definitely* overflow:
```c#
private static uint MixFinal(uint hash)
{
hash ^= hash >> 15;
hash *= Prime2;
hash ^= hash >> 13;
hash *= Prime3;
hash ^= hash >> 16;
return hash;
}
tout comme QueueRound :
c#
private static uint QueueRound(uint hash, uint queuedValue)
{
hash += queuedValue * Prime3;
return Rol(hash, 17) * Prime4;
}
Donc honnêtement je ne vois pas comment cela fonctionnerait :(
Comme ce serait horrible d'ajouter la source HashCode au projet comme le fait Roslyn (avec IL) avec (le plus
Comment envisagez-vous ce fonctionnement ? Qu'écriraient les clients et que feraient les compilateurs en réponse ?
De plus, quelque chose qui résoudrait tout cela est si .Net a déjà des aides publiques exposées sur l'API de surface qui convertissent de uint en int32 (et vice versa) sans débordement.
Ceux-ci existent-ils ? Si c'est le cas, je peux facilement écrire les versions VB, en les utilisant uniquement pour les situations où nous devons passer d'un type à l'autre sans déborder.
Le code de génération de table est-il également désagréable ?
Je le penserais. Je veux dire, pensez à cela du point de vue du client. Ils veulent juste une méthode GetHashCode décente qui soit bien autonome et donne des résultats raisonnables. Avoir cette fonctionnalité aller gonfler leur code avec de la merde auxiliaire va être assez désagréable. C'est également assez mauvais étant donné que l'expérience C# sera très bien.
Vous pourrez peut-être obtenir à peu près le bon comportement de débordement en transtypant vers et depuis une combinaison de types 64 bits signés et non signés. Quelque chose comme ça (non testé et je ne connais pas la syntaxe de coulée VB):
Dim hashCode = -252780983
hashCode = (Int32)((Int32)((Unt64)hashCode * -1521134295) + (UInt64)i.GetHashCode())
Comment savez-vous que ce qui suit ne déborde pas ?
c#
(Int32)((Unt64)hashCode * -1521134295)
Ou le casting final (int32) d'ailleurs ?
Je ne savais pas qu'il utiliserait des opérations de conv vérifiées par débordement. Je suppose que vous pouvez le masquer à 32 bits avant de lancer :
(Int32)(((Unt64)hashCode * -1521134295) & 0xFFFFFFFF)
vraisemblablement 31 bits, car une valeur de uint32.Max déborderait également lors de la conversion en Int32 :)
C'est def possible. Moche... mais possible :) Il y aura beaucoup de castes dans ce code.
D'accord. Je pense avoir une solution viable. Le cœur de l'algorithme que nous générons aujourd'hui est :
c#
hashCode = hashCode * -1521134295 + j.GetHashCode();
Disons que nous faisons des maths 64 bits, mais "hashCode" a été limité à 32 bits. Alors <largest_32_bit> * -1521134295 + <largest_32_bit>
ne débordera pas de 64 bits. Nous pouvons donc toujours faire le calcul en 64 bits, puis réduire à 32 (ou 32 bits) pour s'assurer que le prochain tour ne débordera pas.
Merci!
@MaStr11 @morganbr @sharwell et tout le monde ici. J'ai mis à jour mon code pour générer ce qui suit pour VB :
Dim hashCode As Long = 2118541809
hashCode = (hashCode * -1521134295 + a.GetHashCode()) And Integer.MaxValue
hashCode = (hashCode * -1521134295 + b.GetHashCode()) And Integer.MaxValue
Return CType(hashCode And Integer.MaxValue, Integer)
Quelqu'un peut-il me contrôler pour s'assurer que cela a du sens et ne devrait pas déborder même avec le mode coché activé?
@CyrusNajmabadi , cela ne débordera pas (car Int64.Max = Int32.Max*Int32.Max et vos constantes sont beaucoup plus petites que cela) mais vous masquez le bit haut à zéro, ce n'est donc qu'un hachage de 31 bits. Le fait de laisser le bit haut activé est-il considéré comme un débordement ?
@CyrusNajmabadi hashCode
est un Long
qui peut aller de 0 à Integer.MaxValue
. Pourquoi est-ce que je reçois ça ?
Mais non, il ne peut pas vraiment déborder.
Au fait, je préférerais que Roslyn ajoute un package NuGet plutôt que d'ajouter un hachage sous-optimal.
mais vous masquez le bit de poids fort à zéro, ce n'est donc qu'un hachage de 31 bits. Le fait de laisser le bit haut activé est-il considéré comme un débordement ?
C'est un bon point. Je pense que je pensais à un autre algorithme qui utilisait des uints. Donc, afin de convertir en toute sécurité du long en uint, je devais ne pas inclure le bit de signe. Cependant, comme il s'agit de mathématiques signées, je pense qu'il serait bon de masquer simplement 0xffffffff en veillant à ne conserver que les 32 bits inférieurs après avoir ajouté chaque entrée.
Je préférerais que Roslyn ajoute un package NuGet plutôt que d'ajouter un hachage sous-optimal.
Les utilisateurs peuvent déjà le faire s'ils le souhaitent. Il s'agit de ce qu'il faut faire lorsque les utilisateurs n'ajoutent pas ou ne peuvent pas ajouter ces dépendances. Il s'agit également de fournir un hachage raisonnablement « assez bon » pour les utilisateurs. c'est-à-dire quelque chose de mieux que l'approche commune "x + y + z" que les gens adoptent souvent. Il n'est pas destiné à être « optimal » car il n'y a pas de bonne définition de ce qu'est « optimal » en matière de hachage pour tous les utilisateurs. Notez que l'approche que nous adoptons ici est celle déjà émise par le compilateur pour les types anonymes. Il présente un comportement raisonnablement bon sans ajouter une tonne de complexité au code de l'utilisateur. Au fur et à mesure que de plus en plus d'utilisateurs sont capables d'aller de l'avant, cela peut lentement disparaître et être remplacé par HashCode.Combine pour la plupart des gens.
J'ai donc travaillé un peu et j'ai trouvé ce qui suit qui, je pense, répond à toutes les préoccupations :
Dim hashCode As Long = 2118541809
hashCode = (hashCode * -1521134295 + a.GetHashCode()).GetHashCode()
hashCode = (hashCode * -1521134295 + b.GetHashCode()).GetHashCode()
Return CType(hashCode, Integer)
La pièce qui est intéressante appelle spécifiquement .GetHashCode()
sur la valeur int64 produite par (hashCode * -1521134295 + a.GetHashCode())
. Appeler .GetHashCode sur cette valeur 64 bits a deux bonnes propriétés pour nos besoins. Premièrement, il garantit que hashCode n'y stocke qu'une valeur int32 légale (ce qui rend le cast de retour final toujours sûr à exécuter). Deuxièmement, cela garantit que nous ne perdons aucune information précieuse dans les 32 bits supérieurs de la valeur temp int64 avec laquelle nous travaillons.
@CyrusNajmabadi Proposer en fait d'installer le package est ce que je demandais. Cela m'évite d'avoir à le faire.
Si vous tapez HashCode, alors si System.HashCode est fourni dans un package MS nuget, Roslyn le proposera.
Je veux qu'il génère la surcharge GetHashCode inexistante et installe le package dans la même opération.
Je ne pense pas que ce soit un choix approprié pour la plupart des utilisateurs. L'ajout de dépendances est une opération très lourde à laquelle les utilisateurs ne devraient pas être contraints. Les utilisateurs peuvent décider du bon moment pour faire ces choix, et l'IDE le respectera. C'est l'approche que nous avons adoptée avec toutes nos fonctionnalités jusqu'à présent, et c'est une approche saine que les gens semblent aimer.
Remarque : dans quel package nuget cette API est-elle incluse pour que nous puissions ajouter une référence ?
L'implémentation est dans System.Private.CoreLib.dll, elle ferait donc partie du package d'exécution. Le contrat est System.Runtime.dll.
D'accord. Si tel est le cas, il semble qu'un utilisateur l'obtiendrait s'il passe à un cadre cible plus récent. Ce genre de chose n'est pas du tout une étape que j'aurais le "générer égal + code de hachage" faire au projet d'un utilisateur.
Commentaire le plus utile
Les décisions
AddRange
car le scénario n'est pas clair. Il est peu probable que les tableaux apparaissent très souvent. Et une fois que de plus grands tableaux sont impliqués, la question est de savoir si le calcul doit être mis en cache. Voir la boucle for du côté appelant montre clairement que vous devez y penser.IEnumerable
surchargesAddRange
car elles seraient allouées.Add
qui prendstring
etStringComparison
. Oui, ceux-ci sont probablement plus efficaces que d'appeler via leIEqualityComparer
, mais nous pouvons résoudre ce problème plus tard.GetHashCode
comme obsolète avec erreur est une bonne idée, mais nous irions un peu plus loin et nous nous cacherions également d'IntelliSense.Cela nous laisse avec :
```C#(valeur T11) ;(valeur T11, valeur T22) ;(valeur T11, valeur T22, valeur T33) ;(valeur T11, valeur T22, valeur T33, valeur T44) ;(valeur T11, valeur T22, valeur T33, valeur T44, valeur T55) ;(valeur T11, valeur T22, valeur T33, valeur T44, valeur T55, valeur T66) ;(valeur T11, valeur T22, valeur T33, valeur T44, valeur T55, valeur T66, valeur T77) ;(valeur T1, valeur T22, valeur T33, valeur T44, valeur T55, valeur T66, valeur T77, valeur T88) ;
// vivra dans l'assemblage de base
// .NET Framework : mscorlib
// .NET Core : System.Runtime / System.Private.CoreLib
système d'espace de noms
{
structure publique HashCode
{
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
public statique int Combiner
}
```