Runtime: Vorschlag: Fügen Sie System.HashCode hinzu, um die Generierung guter Hashcodes zu vereinfachen.

Erstellt am 9. Dez. 2016  ·  182Kommentare  ·  Quelle: dotnet/runtime

Update 16.06.17: Auf der Suche nach Freiwilligen

Die API-Form wurde fertiggestellt. Wir entscheiden jedoch immer noch über den besten Hash-Algorithmus aus einer Liste von Kandidaten für die Implementierung, und wir brauchen jemanden, der uns hilft, den Durchsatz/die Verteilung jedes Algorithmus zu messen. Wenn Sie diese Rolle übernehmen möchten , hinterlassen Sie bitte unten einen Kommentar und

Update 13.06.17: Vorschlag angenommen!

Hier ist die API, die von @terrajobst unter https://github.com/dotnet/corefx/issues/14354#issuecomment -308190321 genehmigt wurde:

// 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();
    }
}

Der Originaltext dieses Vorschlags folgt.

Begründung

Das Generieren eines guten Hash-Codes sollte nicht die Verwendung hässlicher magischer Konstanten und das Herumspielen unseres Codes erfordern. Es sollte weniger verlockend sein, eine schlechte, aber prägnante GetHashCode Implementierung zu schreiben, wie zum Beispiel

class Person
{
    public override int GetHashCode() => FirstName.GetHashCode() + LastName.GetHashCode();
}

Vorschlag

Wir sollten einen HashCode Typ hinzufügen, um die Hash-Code-Erstellung zu kapseln und zu vermeiden, dass Entwickler gezwungen werden, sich in die unordentlichen Details zu verwickeln. Hier ist mein Vorschlag, der auf https://github.com/dotnet/corefx/issues/14354#issuecomment -305019329 basiert, mit einigen kleineren Überarbeitungen.

// 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();
    }
}

Bemerkungen

Siehe @terrajobst ‚s Kommentar zu https://github.com/dotnet/corefx/issues/14354#issuecomment -305.019.329 für die Ziele dieser API; alle seine Bemerkungen sind gültig. Auf diese möchte ich aber besonders hinweisen:

  • Die API muss keinen starken kryptografischen Hash erzeugen
  • Die API stellt "einen" Hash-Code bereit, garantiert jedoch keinen bestimmten Hash-Code-Algorithmus. Dies ermöglicht es uns, später einen anderen Algorithmus zu verwenden oder verschiedene Algorithmen auf verschiedenen Architekturen zu verwenden.
  • Die API garantiert, dass innerhalb eines bestimmten Prozesses die gleichen Werte den gleichen Hash-Code ergeben. Unterschiedliche Instanzen derselben App werden aufgrund der Randomisierung wahrscheinlich unterschiedliche Hash-Codes erzeugen. Auf diese Weise können wir sicherstellen, dass Verbraucher Hashwerte nicht beibehalten und sich versehentlich darauf verlassen können, dass sie über mehrere Läufe hinweg stabil sind (oder schlimmer noch, Versionen der Plattform).
api-approved area-System.Numerics up-for-grabs

Hilfreichster Kommentar

Entscheidungen

  • Wir sollten alle AddRange Methoden entfernen, da das Szenario unklar ist. Es ist eher unwahrscheinlich, dass Arrays sehr oft auftauchen. Und wenn es sich um größere Arrays handelt, stellt sich die Frage, ob die Berechnung zwischengespeichert werden soll. Wenn Sie die for-Schleife auf der aufrufenden Seite sehen, wird deutlich, dass Sie darüber nachdenken müssen.
  • Wir möchten auch keine IEnumerable Überladungen zu AddRange hinzufügen, weil sie allokiert würden.
  • Wir glauben nicht, dass wir die Überladung von Add brauchen, die string und StringComparison . Ja, diese sind wahrscheinlich effizienter als Anrufe über IEqualityComparer , aber wir können dies später beheben.
  • Wir halten es für eine gute Idee, GetHashCode mit einem Fehler als veraltet zu markieren, aber wir gehen noch einen Schritt weiter und verstecken uns auch vor IntelliSense.

Dies lässt uns mit:

```C#
// Wird in der Kernbaugruppe leben
// .NET Framework: mscorlib
// .NET Core: System.Runtime / System.Private.CoreLib
Namensraum-System
{
öffentliche Struktur HashCode
{
public static int Kombinieren(T1-Wert1);
public static int Kombinieren(T1-Wert1, T2-Wert2);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7, T8-Wert8);

    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();
}

}
```

Alle 182 Kommentare

Vorschlag: Unterstützung für Hash-Randomisierung hinzufügen

public static HashCode Randomized<T> { get; } // or CreateRandomized<T>
or 
public static HashCode Randomized(Type type); // or CreateRandomized(Type type)

T oder Type type wird benötigt, um denselben randomisierten Hash für denselben Typ zu erhalten.

Vorschlag: Unterstützung für Sammlungen hinzufügen

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

Ich denke, es besteht keine Notwendigkeit für Überladungen Combine(_field1, _field2, _field3, _field4, _field5) da der nächste Code HashCode.Empty.Combine(_field1).Combine(_field2).Combine(_field3).Combine(_field4).Combine(_field5); ohne Combine-Aufrufe inline optimiert werden sollte.

@AlexRadch

Vorschlag: Unterstützung für Sammlungen hinzufügen

Ja, das war Teil meines späteren Plans für diesen Vorschlag. Ich denke, es ist jedoch wichtig, sich darauf zu konzentrieren, wie die API aussehen soll, bevor wir diese Methoden hinzufügen.

Er wollte einen anderen Algorithmus verwenden, wie den Marvin32-Hash, der für Strings in coreclr verwendet wird. Dies würde eine Erweiterung der Größe von HashCode auf 8 Bytes erfordern.

Wie wäre es mit Hash32- und Hash64-Typen, die intern 4 oder 8 Byte Daten speichern würden? Dokumentieren Sie die Vor- und Nachteile jedes einzelnen. Hash64 ist gut für X, aber potenziell langsamer. Hash32 ist schneller, aber möglicherweise nicht so verteilt (oder was auch immer der Kompromiss tatsächlich ist).

Er wollte den Hash-Seed randomisieren, damit Hashes nicht deterministisch sind.

Dies scheint ein nützliches Verhalten zu sein. Aber ich konnte sehen, dass Leute das kontrollieren wollten. Vielleicht sollte es also zwei Möglichkeiten geben, den Hash zu erstellen, eine, die keinen Seed benötigt (und einen zufälligen Seed verwendet) und eine, die die Bereitstellung des Seeds ermöglicht.

Hinweis: Roslyn würde sich freuen, wenn dies in der Fx bereitgestellt werden könnte. Wir fügen eine Funktion hinzu, um einen GetHashCode für den Benutzer auszuspucken. Derzeit generiert es Code wie:

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; }

Dies ist keine großartige Erfahrung und enthüllt viele hässliche Konzepte. Wir würden uns freuen, eine Hash.Whatever-API zu haben, die wir stattdessen aufrufen könnten.

Vielen Dank!

Was ist mit MurmurHash? Es ist relativ schnell und hat sehr gute Hashing-Eigenschaften. Es gibt auch zwei verschiedene Implementierungen, eine, die 32-Bit-Hashes ausspuckt, und eine andere, die 128-Bit-Hashes ausspuckt.

Es gibt auch vektorisierte Implementierungen sowohl für das 32-Bit- als auch für das 128-Bit-Format.

@tannergooding MurmurHash ist schnell, aber nicht sicher, von den Klängen dieses Blogbeitrags .

@jkotas , gab es im JIT seit unseren Diskussionen im letzten Jahr irgendwelche Arbeiten zur Generierung von besserem Code für > 4-Byte-Strukturen auf 32-Bit? Und was halten Sie von

Wie wäre es mit Hash32- und Hash64-Typen, die intern 4 oder 8 Byte Daten speichern würden? Dokumentieren Sie die Vor- und Nachteile jedes einzelnen. Hash64 ist gut für X, aber potenziell langsamer. Hash32 ist schneller, aber möglicherweise nicht so verteilt (oder was auch immer der Kompromiss tatsächlich ist).

Ich denke immer noch, dass es sehr wertvoll wäre, diesen Typ Entwicklern anzubieten, und es wäre großartig, ihn in 2.0 zu haben.

@jamesqo , ich glaube nicht, dass diese Implementierung kryptografisch sicher sein muss (das ist der Zweck der expliziten kryptografischen Hashing-Funktionen).

Dieser Artikel gilt auch für Murmur2. Das Problem wurde im Murmur3-Algorithmus behoben.

das JIT um besseren Code für >4-Byte-Strukturen auf 32-Bit zu generieren seit unseren Diskussionen im letzten Jahr

Mir sind keine bekannt.

Was hältst du von @CyrusNajmabadis Vorschlag

Die Framework-Typen sollten einfache Entscheidungen sein, die in über 95 % der Fälle gut funktionieren. Sie sind vielleicht nicht die schnellsten, aber das ist in Ordnung. Die Wahl zwischen Hash32 und Hash64 ist keine einfache Wahl.

Das ist okay für mich. Aber können wir für diese 95%-Fälle zumindest eine ausreichend gute Lösung haben? Im Moment ist nichts... :-/

hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.s);

@CyrusNajmabadi Warum

Für Nicht-Structs: damit wir nicht auf null prüfen müssen.

Dies kommt dem nahe, was wir auch für anonyme Typen hinter den Kulissen generieren. Ich optimiere den Fall bekannter Nicht-Null-Werte, um Code zu generieren, der für die Benutzer angenehmer wäre. Aber es wäre schön, dafür nur eine eingebaute API zu haben.

Der Aufruf von EqualityComparer.Default.GetHashCode ist ungefähr 10x teurer als die Prüfung auf null... .

Der Aufruf von EqualityComparer.Default.GetHashCode ist ungefähr 10x teurer als die Prüfung auf null.

Klingt nach einem Problem. Wenn es nur eine gute Hash-Code-API gäbe, könnten wir die Fx aufrufen, auf die ich mich verschieben könnte :)

(Außerdem haben wir dieses Problem dann in unseren anonymen Typen, da wir das auch dort generieren).

Ich bin mir nicht sicher, was wir für Tupel tun, aber ich vermute, es ist ähnlich.

Ich bin mir nicht sicher, was wir für Tupel tun, aber ich vermute, es ist ähnlich.

System.Tuple geht aus historischen Gründen durch EqualityComparer<Object>.Default . System.ValueTuple ruft Object.GetHashCode mit Nullprüfung auf - https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/ValueTuple.cs#L809.

Ach nein. Sieht so aus, als ob Tupel einfach "HashHelpers" verwenden kann. Könnte das aufgedeckt werden, damit die Benutzer den gleichen Nutzen haben?

Groß. Ich mache gerne etwas Ähnliches. Ich habe mit unseren anonymen Typen angefangen, weil ich dachte, dass sie vernünftige Best Practices sind. Wenn nicht, ist das in Ordnung. :)

Aber deswegen bin ich nicht hier. Ich bin hier, um ein System zu bekommen, das die Hashes effektiv kombiniert. Wenn/wenn das bereitgestellt werden kann, gehen wir gerne dazu über, das aufzurufen, anstatt in Zufallszahlen fest zu codieren und Hash-Werte selbst zu kombinieren.

Welche API-Form wäre Ihrer Meinung nach am besten für den vom Compiler generierten Code geeignet?

Buchstäblich jede der früher vorgestellten 32-Bit-Lösungen wäre für mich in Ordnung. Verdammt, 64bit-Lösungen sind für mich in Ordnung. Nur eine Art API, die Sie erhalten können, die besagt: "Ich kann Hashes auf eine vernünftige Weise kombinieren und ein vernünftig verteiltes Ergebnis erzeugen".

Ich kann diese Aussagen nicht vereinen:

Wir hatten eine unveränderliche HashCode-Struktur mit einer Größe von 4 Byte. Es hatte eine Combine(int)-Methode, die über einen DJBX33X-ähnlichen Algorithmus den bereitgestellten Hash-Code mit seinem eigenen Hash-Code vermischte und einen neuen HashCode zurückgab.

@jkotas hielt den DJBX33X-ähnlichen Algorithmus nicht für robust genug.

Und

Die Framework-Typen sollten einfache Entscheidungen sein, die in über 95 % der Fälle gut funktionieren.

Können wir nicht einen einfachen 32-Bit-akkumulierenden Hash entwickeln, der in 95% der Fälle gut genug funktioniert? Was sind die Fälle, die hier nicht gut gehandhabt werden, und warum sind sie unserer Meinung nach im Fall von 95 %?

@jkotas , ist die Leistung für diesen Typ wirklich so dies würde viel mehr Zeit als ein paar struct Kopien aufzunehmen. Wenn es sich als Engpass herausstellt, wäre es vernünftig, das JIT-Team zu bitten, 32-Bit-Strukturkopien nach der Veröffentlichung der API zu optimieren, damit sie einen Anreiz haben, anstatt diese API zu blockieren, wenn niemand an der Optimierung arbeitet Kopien?

Können wir nicht einen einfachen 32-Bit-akkumulierenden Hash entwickeln, der in 95% der Fälle gut genug funktioniert?

Wir haben standardmäßig 32-Bit-Hash für Strings akkumulieren wirklich schlecht gebrannt, und deshalb Marvin-Hash für Strings in .NET Core - https://github.com/dotnet/corert/blob/87e58839d6629b5f90777f886a2f52d7a99c076f/src/System.Private.CoreLib/ src/System/Marvin.cs#L25. Ich glaube nicht, dass wir hier denselben Fehler wiederholen wollen.

@jkotas , ist die Leistung für diesen Typ wirklich so

Ich denke nicht, dass die Leistung entscheidend ist. Da es so aussieht, als ob diese API von automatisch generiertem Compilercode verwendet wird, denke ich, dass wir kleineren generierten Code dem Aussehen vorziehen sollten. Das nicht fließende Muster ist kleinerer Code.

Wir wurden wirklich schlecht gebrannt, standardmäßig 32-Bit, das Hash für String ansammelt

Das scheint nicht der 95%-Fall zu sein. Wir sprechen von normalen Entwicklern, die nur einen "gut genug" Hash für all die Typen wollen, bei denen sie heute Dinge manuell tun.

Da es so aussieht, als ob diese API von automatisch generiertem Compilercode verwendet wird, denke ich, dass wir kleineren generierten Code dem Aussehen vorziehen sollten. Das nicht fließende Muster ist kleinerer Code.

Dies ist nicht für den Roslyn-Compiler vorgesehen. Dies wird von der Roslyn-IDE verwendet, wenn wir Benutzern helfen, GetHashCodes für ihre Typen zu generieren. Dies ist Code, den der Benutzer sehen und pflegen muss und der etwas Vernünftiges hat wie:

```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;

Ich meine, wir haben diesen Code bereits im Fx:

https://github.com/dotnet/roslyn/blob/master/src/Compilers/Test/Resources/Core/NetFX/ValueTuple/ValueTuple.cs#L5

Wir denken, es ist gut genug für Tupel. Es ist mir unklar, warum es ein solches Problem sein sollte, es Benutzern zur Verfügung zu stellen, die es für ihre eigenen Typen haben möchten.

Hinweis: Wir haben sogar in Erwägung gezogen, dies in Roslyn zu tun:

c# return (this.A, this.B, this.C).GetHashCode();

Aber jetzt zwingen Sie die Leute, eine (potenziell große) Struktur zu generieren, nur um ein vernünftiges Standard-Hashing-Verhalten zu erhalten.

Wir sprechen von normalen Entwicklern, die nur einen "gut genug" Hash für all die Typen wollen, bei denen sie heute Dinge manuell tun.

Der ursprüngliche String-Hash war ein "gut genug" Hash, der für normale Entwickler gut funktionierte. Aber dann wurde entdeckt, dass ASP.NET-Webserver anfällig für DoS-Angriffe waren, da sie dazu neigen, empfangene Daten in Hashtables zu speichern. So wurde der "gut genug" Hash im Grunde zu einem schlechten Sicherheitsproblem.

Wir denken, es ist gut genug für Tupel

Nein unbedingt. Wir haben eine Backstop-Messung für Tupel gemacht, um den Hashcode randomisiert zu machen, der uns die Möglichkeit gibt, den Algorithmus später zu ändern.

     return Hash.Combine(this.A?.GetHashCode() ?? 0,
                         this.B?.GetHashCode() ?? 0,
                         this.C?.GetHashCode() ?? 0);

Das sieht für mich vernünftig aus.

Ich verstehe Ihre Position nicht. Du scheinst zwei Dinge zu sagen:

Der ursprüngliche String-Hash war ein "gut genug" Hash, der für normale Entwickler gut funktionierte. Aber dann wurde entdeckt, dass ASP.NET-Webserver anfällig für DoS-Angriffe waren, da sie dazu neigen, empfangene Daten in Hashtables zu speichern. So wurde der "gut genug" Hash im Grunde zu einem schlechten Sicherheitsproblem.

Ok, wenn das der Fall ist, dann stellen wir einen Hash-Code bereit, der gut für Leute ist, die Sicherheits-/DoS-Bedenken haben.

Die Framework-Typen sollten einfache Entscheidungen sein, die in über 95 % der Fälle gut funktionieren.

Ok, wenn das der Fall ist, stellen wir einen Hash-Code bereit, der für die 95% der Fälle gut genug ist. Personen, die Sicherheits-/DoS-Bedenken haben, können die zu diesem Zweck dokumentierten speziellen Formulare verwenden.

Nein unbedingt. Wir haben eine Backstop-Messung für Tupel gemacht, um den Hashcode randomisiert zu machen, der uns die Möglichkeit gibt, den Algorithmus später zu ändern.

Okay. Können wir das offenlegen, damit Benutzer denselben Mechanismus verwenden können?

--
Ich habe hier wirklich Probleme, weil es so klingt, als würden wir sagen "weil wir keine universelle Lösung finden können, muss jeder seine eigene rollen". Das scheint einer der schlimmsten Orte zu sein. Denn sicherlich denken die meisten unserer Kunden nicht daran, ihren eigenen "Marvin-Hash" für DoS-Bedenken zu rollen. Sie fügen nur Feld-Hashes hinzu, xoring oder kombiniert sie auf andere Weise schlecht zu einem endgültigen Hash.

Wenn uns der Fall von 95 % wichtig ist, sollten wir einfach einen allgemein guten Hasch herstellen. WENN uns der 5%-Fall wichtig ist, können wir dafür eine spezialisierte Lösung liefern.

Das sieht für mich vernünftig aus.

Toll :) Können wir dann aussetzen:

```c#
Namespace System.Numerics.Hashing
{
interne statische Klasse 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);

Dies hätte den Vorteil, dass es für die überwiegende Mehrheit der Fälle wirklich "gut genug" ist, während es gleichzeitig den guten Weg der Initialisierung mit zufälligen Werten führt, damit sie keine Abhängigkeiten von nicht zufälligen Hashes eingehen.

Personen, die Sicherheits-/DoS-Bedenken haben, können die zu diesem Zweck dokumentierten speziellen Formulare verwenden.

Jede ASP.NET-App hat Sicherheits-/DoS-Bedenken.

Toll :) Können wir dann aussetzen:

Dies unterscheidet sich von dem, was ich gesagt habe, ist vernünftig.

Was halten Sie von https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.HashCodeCombiner.Sources/HashCodeCombiner.cs . Es ist das, was heute in ASP.NET intern an vielen Stellen verwendet wird, und damit wäre ich ziemlich zufrieden (außer dass die Kombinationsfunktion stärker sein muss - Implementierungsdetails, die wir weiter optimieren können).

@jkotas Ich habe das gehört :p

Das Problem hier ist also, dass Entwickler nicht wissen, wann sie anfällig für DoS-Angriffe sind, weil sie nicht darauf achten, weshalb wir die Strings auf Marvin32 umgestellt haben.

Wir sollten nicht den Weg einschlagen, zu sagen "95 % der Fälle spielen keine Rolle", denn wir haben keine Möglichkeit, dies zu beweisen, und wir müssen auf der Seite der Vorsicht sein, selbst wenn dies Leistungskosten verursacht. Wenn Sie sich davon entfernen möchten, muss die Hash-Code-Implementierung vom Crypto Board überprüft werden, und nicht nur wir entscheiden "Das sieht gut genug aus".

Jede ASP.NET-App hat Sicherheits-/DoS-Bedenken.

Okay. Wie gehen Sie heute mit dem Problem um, dass niemand Hilfe bei Hashcodes hat und daher wahrscheinlich die Dinge schlecht macht? Es war eindeutig akzeptabel, diesen Zustand der Welt zu haben. Was schadet also, wenn ein vernünftiges Hashing-System bereitgestellt wird, das wahrscheinlich besser funktioniert als das, was die Leute heute von Hand rollen?

weil wir das nicht beweisen können, und wir müssen auf der sicheren Seite sein, selbst wenn es Leistungskosten hat

Wenn Sie etwas nicht bereitstellen, werden die Leute weiterhin Dinge schlecht machen. Die Ablehnung des "Guten", weil es nichts Perfektes gibt, bedeutet nur den schlechten Status quo, den wir heute haben.

Jede ASP.NET-App hat Sicherheits-/DoS-Bedenken.

Können Sie das erklären? Wie ich es verstehe, haben Sie ein DoS-Problem, wenn Sie beliebige Eingaben akzeptieren und diese dann in einer Datenstruktur speichern, die schlecht funktioniert, wenn die Eingaben speziell gestaltet werden können. Ok, ich verstehe, dass das ein Problem mit den Zeichenfolgen ist, die man in Web-Szenarien erhält, die vom Benutzer stammen.

Wie trifft das auf den Rest der Typen zu, die in diesem Szenario nicht verwendet werden?

Wir haben diese Arten von Sets:

  1. Benutzertypen, die DoS-sicher sein müssen. Im Moment liefern wir nichts, um zu helfen, also sind wir bereits an einem schlechten Ort, da die Leute wahrscheinlich nicht das Richtige tun.
  2. Benutzertypen, die nicht DoS-sicher sein müssen. Im Moment liefern wir nichts, um zu helfen, also sind wir bereits an einem schlechten Ort, da die Leute wahrscheinlich nicht das Richtige tun.
  3. Framework-Typen, die DoS-sicher sein müssen. Im Moment haben wir sie DoS-sicher gemacht, aber durch APIs, die wir nicht offenlegen.
  4. Framework-Typen, die nicht DoS-sicher sein müssen. Im Moment haben wir ihnen Hashes gegeben, aber durch APIs, die wir nicht verfügbar machen.

Grundsätzlich halten wir diese Fälle für wichtig, aber nicht wichtig genug, um den Benutzern tatsächlich eine Lösung für den Umgang mit '1' oder '2' zu bieten. Da wir befürchten, dass eine Lösung für '2' nicht gut für '1' ist, werden wir sie erst gar nicht bereitstellen. Und wenn wir nicht einmal bereit sind, eine Lösung für '1' anzubieten, fühlt es sich an, als wären wir in einer unglaublich seltsamen Position. Wir machen uns Sorgen um DoSing und ASP, aber nicht genug, um Menschen tatsächlich zu helfen. Und weil wir den Leuten damit nicht helfen, sind wir auch nicht bereit, bei den Nicht-DoS-Fällen zu helfen.

--

Wenn diese beiden Fälle wichtig sind (was ich gerne akzeptieren möchte), warum dann nicht einfach zwei APIs geben? Dokumentieren Sie sie. Machen Sie ihnen klar, wozu sie dienen. Wenn die Leute sie richtig verwenden, großartig . Wenn die Leute sie nicht richtig verwenden, ist das immer noch in Ordnung. Schließlich machen sie die Dinge heute wahrscheinlich sowieso nicht richtig, also wie geht es ihnen noch schlimmer?

Was denkst du über

Ich habe keine Meinung so oder so. Wenn es sich um eine API handelt, die Kunden verwenden können, die eine akzeptable Leistung bietet und die eine einfache API mit klarem Code an ihrer Seite bereitstellt, dann ist das meiner Meinung nach in Ordnung.

Ich denke, es wäre schön, ein einfaches statisches Formular zu haben, das den 99%igen Fall behandelt, einen Satz von Feldern/Eigenschaften in einer geordneten Weise kombinieren zu wollen. Es scheint, als könnte so etwas ziemlich einfach zu diesem Typ hinzugefügt werden.

Ich denke, es wäre schön, eine einfache statische Form zu haben

Zustimmen.

Ich denke, es wäre schön, ein einfaches statisches Formular zu haben, das den 99%igen Fall behandelt, einen Satz von Feldern/Eigenschaften in einer geordneten Weise kombinieren zu wollen. Es scheint, als könnte so etwas ziemlich einfach zu diesem Typ hinzugefügt werden.

Zustimmen.

Ich bin bereit, Sie beide auf halbem Weg zu treffen, weil ich wirklich möchte, dass eine Art API durchkommt. @jkotas Ich verstehe immer noch nicht, dass Sie dagegen sind, eine unveränderliche h.Combine(a).Combine(b) (unveränderliche Version) ist kürzer als h.Combine(a); h.Combine(b); (veränderlich .) Ausführung)).

Das heißt, ich bin bereit, zurück zu gehen:

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
}

Erscheint dies vernünftig?

Ich kann meinen Beitrag gerade nicht bearbeiten, aber ich habe gerade festgestellt, dass nicht alle Methoden T akzeptieren können. In diesem Fall können wir einfach 8 Überladungen haben, die alle Ints akzeptieren und den Benutzer zwingen, GetHashCode aufzurufen.

Wenn diese beiden Fälle wichtig sind (was ich gerne akzeptieren möchte), warum dann nicht einfach zwei APIs geben? Dokumentieren Sie sie. Machen Sie ihnen klar, wozu sie dienen. Wenn die Leute sie richtig verwenden, großartig. Wenn die Leute sie nicht richtig verwenden, ist das immer noch in Ordnung. Schließlich machen sie die Dinge heute wahrscheinlich sowieso nicht richtig, also wie geht es ihnen noch schlimmer?

Weil die Leute die Dinge nicht richtig benutzen, wenn sie dort sind. Nehmen wir ein einfaches Beispiel, XSS. Von Anfang an hatten sogar Webformulare die Möglichkeit, die Ausgabe in HTML zu kodieren. Allerdings kannten die Entwickler das Risiko nicht, wussten nicht, wie man es richtig macht, und fanden es erst heraus, als es zu spät war, ihre App veröffentlicht wurde und oops, jetzt wurde ihr Authentifizierungs-Cookie aufgehoben.

Den Menschen eine Sicherheitswahl zu geben, setzt voraus, dass sie

  1. Kennen Sie das Problem.
  2. Verstehen Sie, was die Risiken sind.
  3. Kann diese Risiken einschätzen.
  4. Kann leicht das Richtige finden.

Diese Annahmen gelten im Allgemeinen nicht für die Mehrheit der Entwickler, sie erfahren das Problem erst, wenn es zu spät ist. Entwickler gehen nicht zu Sicherheitskonferenzen, lesen keine Whitepaper und verstehen die Lösungen nicht. Also haben wir im ASP.NET HashDoS-Szenario die Wahl für sie getroffen, wir haben sie standardmäßig geschützt, weil das richtig war und die größte Wirkung hatte. Wir haben es jedoch nur auf Strings angewendet, und das führte dazu, dass Leute, die benutzerdefinierte Klassen aus Benutzereingaben erstellten, an einem schlechten Ort waren. Wir sollten das Richtige tun und helfen, diese Kunden jetzt zu schützen und es zum Standard zu machen, um Erfolg und nicht Misserfolg zu erzielen. Beim API-Design für die Sicherheit geht es manchmal nicht um die Wahl, sondern darum, dem Benutzer zu helfen, ob er es weiß oder nicht.

Ein Benutzer kann jederzeit einen nicht sicherheitsorientierten Hash erstellen; also die zwei möglichkeiten gegeben

  1. Das Standard-Hash-Dienstprogramm ist nicht sicherheitsbewusst; Benutzer kann eine sicherheitsbewusste Hash-Funktion erstellen
  2. Das Standard-Hash-Dienstprogramm ist sicherheitsbewusst; Benutzer kann eine benutzerdefinierte, nicht sicherheitsbewusste Hash-Funktion erstellen

Dann ist die zweite wahrscheinlich besser; und was vorgeschlagen wird, hätte nicht die perfekte Auswirkung eines vollständigen Krypto-Hashs; also ist es ein guter kompromiss?

Eine der ständigen Fragen in diesen Threads war, welcher Algorithmus für jeden perfekt ist. Ich denke, man kann mit Sicherheit sagen, dass es keinen einzigen perfekten Algorithmus gibt. Ich glaube jedoch nicht, dass uns das davon abhalten sollte, etwas Besseres als Code @CyrusNajmabadi gezeigt hat, der dazu neigt, eine schlechte Entropie für allgemeine .NET-Eingaben sowie andere häufige Hasher-Bugs (wie das Verlieren von Eingabedaten oder leichtes Arbeiten) zu haben rücksetzbar).

Ich möchte ein paar Optionen vorschlagen, um das Problem des "besten Algorithmus" zu umgehen:

  1. Explizite Auswahlmöglichkeiten: Ich plane, demnächst einen API-Vorschlag für eine Reihe von nicht-kryptografischen Hashes zu versenden (vielleicht zum Beispiel xxHash, Marvin32 und SpookyHash). Eine solche API hat eine etwas andere Verwendung als ein HashCode- oder HashCodeHelper-Typ, aber der Diskussion halber nehmen wir an, wir können diese Unterschiede herausfinden. Wenn wir diese API für GetHashCode verwenden:

    • Der generierte Code beschreibt eindeutig, was er tut. Wenn Roslyn Marvin32.Create(); generiert, können erfahrene Benutzer wissen, was sie tun, und sie können ihn bei Bedarf problemlos auf einen anderen Algorithmus in der Suite ändern.

    • Das bedeutet, dass wir uns keine Sorgen um Breaking Changes machen müssen. Wenn wir mit einem nicht-randomisierenden/schlechten Entropie/langsamen Algorithmus beginnen, können wir Roslyn einfach aktualisieren, um etwas anderes in neuem Code zu generieren. Alter Code verwendet weiterhin den alten Hash und neuer Code verwendet den neuen Hash. Entwickler (oder ein Roslyn-Code-Fix) können den alten Code ändern, wenn sie möchten.

    • Der größte Nachteil, den ich mir vorstellen kann, ist, dass einige der Optimierungen, die wir für GetHashCode wünschen, für andere Algorithmen schädlich sein könnten. Während beispielsweise ein interner 32-Bit-Zustand mit unveränderlichen Strukturen gut funktioniert, kann ein interner 256-Bit-Zustand in (sagen wir) CityHash eine Menge Zeit mit dem Kopieren verschwenden.

  1. Randomisierung: Beginnen Sie mit einem ordnungsgemäß randomisierten Algorithmus (der Code, den @CyrusNajmabadi mit einem zufälligen Anfangswert gezeigt hat, zählt nicht, da es wahrscheinlich möglich ist, die Zufälligkeit auszuwaschen). Dadurch wird sichergestellt, dass wir die Implementierung ohne Kompatibilitätsprobleme ändern können. Wir müssten immer noch sehr sensibel auf Leistungsänderungen reagieren, wenn wir den Algorithmus ändern. Dies wäre jedoch auch ein potenzieller Vorteil, da wir Entscheidungen pro Architektur (oder sogar pro Gerät) treffen könnten. Diese Seite zeigt beispielsweise, dass xxHash auf einem x64-Mac am schnellsten ist, während SpookyHash auf Xbox und iPhone am schnellsten ist. Wenn wir diesen Weg gehen, um irgendwann die Algorithmen zu ändern, müssen wir möglicherweise darüber nachdenken, eine API zu entwickeln, die immer noch eine angemessene Leistung bietet, wenn ein interner Zustand von mehr als 64 Bit vorhanden ist.

CC @bartonjs , @terrajobst

@morganbr Es gibt keinen einzigen perfekten Algorithmus, aber ich denke, dass es das verfügbar gemacht wird. Zu haben , ist eine Reihe von Algorithmen zusätzlich zu , dass für fortgeschrittene Anwendungen in Ordnung. Aber es sollte nicht die einzige Option sein, ich sollte nicht lernen müssen, wer Marvin ist, nur damit ich meine Objekte in ein Dictionary .

Ich sollte nicht wissen müssen, wer Marvin ist, nur damit ich meine Objekte in ein Wörterbuch eintragen kann.

Ich mag die Art, wie du das ausdrückst. Mir gefällt auch, dass Sie das Wörterbuch selbst erwähnt haben. IDictionary ist etwas, das Tonnen von verschiedenen Impls mit allen möglichen unterschiedlichen Qualitäten haben kann (siehe die Sammlungs-APIs in vielen Plattformen). Wir bieten jedoch immer noch nur ein grundlegendes 'Wörterbuch', das insgesamt eine anständige Arbeit leistet, auch wenn es möglicherweise nicht in jeder Kategorie hervorragend ist.

Ich denke, das ist es, wonach eine Menge Leute in einer Hashing-Bibliothek suchen. Etwas, das die Arbeit erledigt, auch wenn es nicht für jeden Zweck perfekt ist.

@morganbr Ich denke, die Leute wollen einfach eine Möglichkeit, GetHashCode zu schreiben, der besser ist als das, was sie heute tun (normalerweise eine Kombination aus mathematischen Operationen, die sie aus dem Internet kopiert haben). Wenn Sie nur ein grundlegendes Impl davon bereitstellen können, das gut läuft, werden die Leute glücklich sein. Sie können dann eine API hinter den Kulissen für fortgeschrittene Benutzer haben, wenn sie einen starken Bedarf an bestimmten Hashing-Funktionen haben.

Mit anderen Worten, die Leute, die heute Hashcodes schreiben, werden nicht wissen oder sich darum kümmern, warum sie Spooky vs Marvin vs Murmur wollen. Nur jemand, der einen bestimmten Bedarf an einem dieser spezifischen Hash-Codes hat, würde suchen. Aber viele Leute müssen sagen: "Hier ist der Zustand meines Objekts, geben Sie mir eine Möglichkeit, einen gut verteilten Hash zu erzeugen, der schnell ist, den ich dann mit Wörterbüchern verwenden kann und der mich vermutlich daran hindert, DOSiert zu werden, wenn ich dazu komme nicht vertrauenswürdige Eingaben zu nehmen, zu hashen und zu speichern".

@CyrusNajmabadi Das Problem ist, dass wir, wenn wir unsere aktuellen Vorstellungen von Kompatibilität in die Zukunft

Once kann argumentieren, dass es einfach wird, die Implementierung zu ändern, wenn es als stabil-randomisierte Methode beginnt, da Sie sich sowieso nicht auf den Wert von Lauf zu Lauf verlassen können. Aber wenn wir ein paar Jahre später feststellen, dass es einen Algorithmus gibt, der einen so guten, wenn nicht sogar besseren Ausgleich von Hash-Buckets mit einer im Allgemeinen besseren Leistung bietet, aber eine Struktur mit einer Liste erstellt\

Nach Morgans Vorschlag wird der Code, den Sie heute schreiben, für immer dieselben Leistungsmerkmale aufweisen. Für die Anwendungen, die besser hätten werden können, ist dies bedauerlich. Für die Anwendungen, die schlimmer geworden wären, ist das fantastisch. Aber wenn wir den neuen Algorithmus finden, bekommen wir ihn eingecheckt, und wir ändern Roslyn (und schlagen eine Änderung in ReSharper/etc vor), um Dinge mit NewAwesomeThing2019 anstelle von SomeThingThatWasConsideredAwesomeIn2018 zu generieren.

So etwas wie diese Super-Blackbox kann man nur einmal machen. Und dann bleiben wir für immer dabei. Dann schreibt jemand die nächste, die eine bessere durchschnittliche Leistung hat, also gibt es zwei Black-Box-Implementierungen, von denen Sie nicht wissen, warum Sie sich zwischen ihnen entscheiden sollten. Und dann... und dann... .

Sicher, Sie wissen vielleicht nicht, warum Roslyn/ReSharper/etc GetHashCode automatisch für Sie geschrieben hat, indem sie Marvin32 oder Murmur oder FastHash oder eine Kombination/Bedingung basierend auf IntPtr.Size verwendet. Aber Sie haben die Macht, es zu untersuchen. Und Sie haben die Möglichkeit, es später an Ihren Typen zu ändern, wenn neue Informationen bekannt werden ... aber wir haben Ihnen auch die Möglichkeit gegeben, es gleich zu lassen. (Es wäre traurig, wenn wir dies schreiben würden, und in 3 Jahren vermeiden Roslyn/ReSharper/etc explizit, es zu nennen, weil der neue Algorithmus so viel besser ist ... Normalerweise).

@bartonjs Was unterscheidet Hashing von all den Orten, an denen .Net einen Black-Box-Algorithmus oder eine Datenstruktur bereitstellt? Zum Beispiel Sortierung (Introsort), Dictionary (Array-basierte separate Verkettung), StringBuilder (verknüpfte Liste mit 8k Chunks), die meisten von LINQ.

Wir haben uns das heute genauer angeschaut. Entschuldigung für die Verzögerung und das Hin und Her zu diesem Thema.

Anforderungen

  • Für wen ist die API?

    • Die API muss keinen starken kryptografischen Hash erzeugen

    • Aber: Die API muss gut genug sein, damit wir sie im Framework selbst verwenden können (zB in der BCL und ASP.NET)

    • Dies bedeutet jedoch nicht, dass wir die API überall verwenden müssen. Es ist in Ordnung, wenn es Teile des FX gibt, in denen wir einen benutzerdefinierten verwenden möchten, entweder aus Sicherheits- / DOS-Risiken oder aus Gründen der Leistung. Ausnahmen wird es immer geben .

  • Was sind die gewünschten Eigenschaften dieses Hashs?

    • Alle Bits im Eingang werden verwendet

    • Das Ergebnis ist gut verteilt

    • Die API stellt "einen" Hash-Code bereit, garantiert jedoch keinen bestimmten Hash-Code-Algorithmus. Dies ermöglicht es uns, später einen anderen Algorithmus zu verwenden oder verschiedene Algorithmen auf verschiedenen Architekturen zu verwenden.

    • Die API garantiert, dass innerhalb eines bestimmten Prozesses die gleichen Werte den gleichen Hash-Code ergeben. Unterschiedliche Instanzen derselben App werden aufgrund der Randomisierung wahrscheinlich unterschiedliche Hash-Codes erzeugen. Auf diese Weise können wir sicherstellen, dass Verbraucher Hashwerte nicht beibehalten und sich versehentlich darauf verlassen können, dass sie über mehrere Läufe hinweg stabil sind (oder schlimmer noch, Versionen der Plattform).

API-Form

```C#
// Wird in der Kernbaugruppe leben
// .NET Framework: mscorlib
// .NET Core: System.Runtime / System.Private.CoreLib
Namensraum-System
{
öffentliche Struktur HashCode
{
public static int Kombinieren(T1-Wert1);
public static int Kombinieren(T1-Wert1, T2-Wert2);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7, T8-Wert8);

    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);
}

Der kompliziertere Fall ist, wenn der Entwickler die Berechnung des Hashs optimieren muss. Die Idee ist, dass die Aufrufseite den gewünschten Hash und nicht das Objekt / den Wert übergibt, wie folgt:

```C#
öffentliche Teilklasse Kunde
{
öffentliche Überschreibung int GetHashCode() =>
HashCode.Combine(
Ausweis,
StringComparer.OrdinalIgnoreCase.GetHashCode(FirstName),
StringComparer.OrdinalIgnoreCase.GetHashCode(Nachname),
);
}

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();
    }
}

Nächste Schritte

Dieses Thema wird weiterhin offen bleiben. Um die API zu implementieren, müssen wir entscheiden, welcher Algorithmus verwendet werden soll.

@morganbr macht einen Vorschlag für gute Kandidaten. Im Allgemeinen möchten wir keinen Hashing-Algorithmus von Grund auf neu schreiben – wir möchten einen bekannten verwenden, dessen Eigenschaften gut verstanden sind.

Wir sollten jedoch die Implementierung für typische .NET-Workloads messen und sehen, welcher Algorithmus gute Ergebnisse liefert (Durchsatz und Verteilung). Es ist wahrscheinlich, dass sich die Antworten je nach CPU-Architektur unterscheiden, daher sollten wir dies bei der Messung berücksichtigen.

@jamesqo , hast du noch Interesse in diesem Bereich zu arbeiten? Bitte aktualisieren Sie in diesem Fall das Angebot entsprechend.

@terrajobst , wir möchten vielleicht auch public static int Combine<T1>(T1 value); . Ich weiß, es sieht ein bisschen komisch aus, aber es würde eine Möglichkeit bieten, Bits von etwas mit einem begrenzten Eingabe-Hash-Raum zu diffundieren. Viele Enumerationen haben beispielsweise nur wenige mögliche Hashes und verwenden nur die unteren paar Bits des Codes. Einige Sammlungen basieren auf der Annahme, dass Hashes über einen größeren Raum verteilt sind, sodass die Verteilung der Bits dazu beitragen kann, dass die Sammlung effizienter arbeitet.

public void Add(string value, StrinComparison comparison);

Nichts: Der Parameter StringComparison sollte comparisonType heißen, um der Benennung zu entsprechen, die überall sonst verwendet wird, wo StringComparison als Parameter verwendet wird.

Die Kriterien, die uns bei der Auswahl von Algorithmen helfen würden, wären:

  1. Hat der Algorithmus einen guten Lawineneffekt? Das heißt, hat jedes Eingabebit eine 50%ige Chance, jedes Ausgabebit umzudrehen? Diese Seite hat eine Studie über mehrere beliebte Algorithmen.
  2. Ist der Algorithmus für kleine Eingaben schnell? Da HashCode.Combine im Allgemeinen 8 oder weniger Ints verarbeitet, kann die Startzeit wichtiger sein als der Durchsatz. Diese Site hat einen interessanten Datensatz, um mit zu beginnen. Hier benötigen wir möglicherweise auch unterschiedliche Antworten für verschiedene Architekturen oder andere Pivots (OS, AoT vs. JIT usw.).

Was wir wirklich gerne sehen würden, sind Leistungszahlen für Kandidaten, die in C# geschrieben wurden, damit wir einigermaßen sicher sein können, dass ihre Eigenschaften für .NET Bestand haben. Wenn Sie einen Kandidaten schreiben und wir ihn dafür nicht auswählen, wird das immer noch nützliche Arbeit sein, wenn ich tatsächlich den API-Vorschlag für die nicht-kryptografische Hash-API zusammenstelle.

Hier sind einige Kandidaten, von denen ich denke, dass sie es wert sind, bewertet zu werden (aber zögern Sie nicht, andere vorzuschlagen):

  • Marvin32 (wir haben hier bereits eine C#-Implementierung). Wir wissen, dass es für String.GetHashCode schnell genug ist und wir glauben, dass es HashDoS-resistent ist
  • xxHash32 (Schnellster Algorithmus auf x86 hier , der laut SMHasher höchste Qualität hat)
  • FarmHash (Schnellste auf x64 hier . Ich habe keinen guten Indikatoren für die Qualität für sie gefunden. Dies könnte man in C # zu schreiben hart sein , obwohl)
  • xxHash64 (auf 32 Bit gekürzt) (Dies ist kein klarer Geschwindigkeitsgewinner, aber möglicherweise einfach zu bewerkstelligen, wenn wir bereits xxHash32 haben)
  • SpookyHash (Eher gut bei größeren Datensätzen)

Schade, dass die Add Methoden nicht den Rückgabetyp ref HashCode und ref this damit sie flüssig verwendet werden können.

Würden readonly ref Rückgaben dies zulassen? /cc @jaredpar @VSadov

WARNUNG: Wenn jemand eine Hash-Implementierung aus einer bestehenden Codebasis irgendwo im Internet auswählt, behalten Sie bitte den Link zur Quelle und überprüfen Sie die Lizenz (wir müssen dies auch tun).

Wenn die Lizenz nicht kompatibel ist, müssen wir den Algorithmus möglicherweise von Grund auf neu schreiben.

IMO sollte die Verwendung der Add-Methoden äußerst selten sein. Es wird für sehr fortgeschrittene Szenarien sein, und die Notwendigkeit, "fließend" zu sein, wird nicht wirklich vorhanden sein.

Für die üblichen Anwendungsfälle für 99% aller Benutzercode-Fälle sollte man in der Lage sein, einfach => HashCode.Combine(...) und gut zu sein.

@morganbr

wir könnten auch public static int Combine<T1>(T1 value); . Ich weiß, es sieht ein bisschen komisch aus, aber es würde eine Möglichkeit bieten, Bits von etwas mit einem begrenzten Eingabe-Hash-Raum zu diffundieren

Sinn ergeben. Ich habe es hinzugefügt.

@justinvp

Nichts: Der Parameter StringComparison sollte comparisonType heißen, um der Benennung zu entsprechen, die überall sonst verwendet wird, wo StringComparison als Parameter verwendet wird.

Fest.

@CyrusNajmabadi

IMO, die Verwendung der Add Methoden sollte äußerst ungewöhnlich sein. Es wird für sehr fortgeschrittene Szenarien sein, und die Notwendigkeit, "fließend" zu sein, wird nicht wirklich vorhanden sein.

Einverstanden.

@benaadams - re: ref gibt this von Add - nein, this kann nicht von ref in struct-Methoden zurückgegeben werden, da es ein rValue oder ein temp sein kann.

```C#
ref var r = (neues T()).ReturnsRefThis();

// r bezieht sich hier auf eine Variable. Welcher? Was ist der Umfang/die Lebensdauer?
r = Etwas Anderes ();
```

Im Fall ist es für Vergleichszwecke nützlich, vor einigen Jahren ich die portierte Jenkins lookup3 Hash - Funktion ( C - hier .

Ich wundere mich über Sammlungen:

@terrajobst

c# public void Add<T>(T[] value);

Warum gibt es eine Überladung für Arrays, aber keine für allgemeine Sammlungen (zB IEnumerable<T> )?

Ist es nicht verwirrend, dass sich HashCode.Combine(array) und hashCode.Add((object)array) eine Richtung verhalten (Verwendung von Referenzgleichheit) und hashCode.Add(array) sich

@CyrusNajmabadi

Für die üblichen Anwendungsfälle für 99% aller Benutzercode-Fälle sollte man in der Lage sein, einfach => HashCode.Combine(...) und gut zu sein.

Wenn es wirklich darum geht, Combine in 99 % der Anwendungsfälle (und nicht etwa in 80 %) verwenden zu können, dann sollte Combine irgendwie Hashing-Sammlungen basierend auf den Werten unterstützen in der Sammlung? Vielleicht sollte es eine separate Methode geben, die dies tut (entweder eine Erweiterungsmethode oder eine statische Methode auf HashCode )?

Wenn Add ein Power-Szenario ist, sollten wir davon ausgehen, dass der Benutzer zwischen Object.GetHashCode und dem Kombinieren einzelner Elemente von Sammlungen wählen sollte? Wenn es helfen würde, könnten wir erwägen, das Array (und potenzielle IEnumerable) Versionen umzubenennen. Etwas wie:
c# public void AddEnumerableHashes<T>(IEnumerable<T> enumerable); public void AddEnumerableHashes<T>(T[] array); public void AddEnumerableHashes<T>(T[] array, int index, int length);
Ich frage mich, ob wir mit IEqualityComparers auch Überladungen brauchen würden.

Vorschlag: Lassen Sie die Builder-Struktur IEnumerable implementieren, um die Syntax des Sammlungsinitialisierers zu unterstützen:

C# return new HashCode { SomeField, OtherField, { SomeString, StringComparer.UTF8 }, { SomeHashSet, HashSet<int>.CreateSetComparer() } }.GetHashCode()

Das ist viel eleganter, als Add() von Hand aufzurufen (insbesondere braucht man keine temporäre Variable) und hat immer noch keine Zuweisungen.

mehr Details

@SLaks Vielleicht könnte diese schönere Syntax auf https://github.com/dotnet/csharplang/issues/455 warten (vorausgesetzt, dass der Vorschlag unterstützt wird), damit HashCode nicht gefälschte IEnumerable implementieren müsste

Wir haben uns entschieden, GetHashCode() nicht zu überschreiben, um den Hash-Code zu erzeugen, da dies sowohl in Bezug auf die Benennung als auch aus verhaltenstechnischer Sicht seltsam wäre (GetHashCode() sollte den Hash-Code des Objekts zurückgeben, nicht den berechneten).

Ich finde es seltsam, dass GetHashCode den berechneten Hashcode nicht zurückgibt. Ich denke, das wird die Entwickler verwirren. Zum Beispiel hat @SLaks es bereits in seinem Vorschlag verwendet, anstatt ToHashCode .

@justinvp Wenn GetHashCode() den berechneten Hash-Code nicht zurückgibt , sollte er wahrscheinlich mit [Obsolete] und [EditorBrowsable(Never)] .

Auf der anderen Seite sehe ich keinen Schaden darin, den berechneten Hash-Code zurückzugeben.

@terrajobst

Wir haben uns entschieden, GetHashCode() nicht zu überschreiben, um den Hash-Code zu erzeugen, da dies sowohl von der Benennung als auch vom Standpunkt des Verhaltens aus seltsam wäre ( GetHashCode() sollte den Hash-Code des Objekts zurückgeben, nicht den einen berechnet).

Ja, GetHashCode() sollte den Hashcode des Objekts zurückgeben, aber gibt es einen Grund, warum die beiden Hashcodes unterschiedlich sein sollten? Es ist immer noch richtig, da zwei Instanzen von HashCode mit demselben internen Status denselben Wert von GetHashCode() .

@terrajobst Ich habe gerade deinen Kommentar gesehen. Verzeihen Sie mir die verspätete Antwort, ich habe mir die Benachrichtigung nur langsam angesehen, weil ich dachte, es würde nur mehr hin und her gehen, das nirgendwo hinführt. Schön zu sehen, dass dem nicht so ist! :tada:

Ich würde es gerne aufnehmen und die Durchsatz-/Verteilungsmessung durchführen (ich nehme an, das meinten Sie mit "interessiert an der Arbeit in diesem Bereich"). Geben Sie mir jedoch eine Sekunde, um alle Kommentare hier zu Ende zu lesen.

@terrajobst

Können wir uns ändern

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

zu

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

? Ich habe Add -> AddRange , um das von @svick erwähnte Verhalten zu vermeiden. Ich habe die byte Überladungen entfernt, da wir uns mit typeof(T) == typeof(byte) innerhalb der Methode spezialisieren können, wenn wir etwas bytespezifisches tun müssen. Außerdem habe ich value -> values und length -> count geändert. Es ist auch sinnvoll, eine Vergleicherüberladung zu haben.

@terrajobst Kannst du mich daran erinnern, warum

        public void Add(string value);
        public void Add(string value, StringComparison comparisonType);

ist notwendig, wenn wir haben

        public void Add<T>(T value);
        public void Add<T>(T value, IEqualityComparer<T> comparer);

?

@svick

@justinvp Wenn GetHashCode() den berechneten Hash-Code nicht zurückgibt, sollte er wahrscheinlich als [Obsolete] und [EditorBrowsable(Never)] markiert werden.

:+1:

@terrajobst Können wir zu einer impliziten Konvertierung von HashCode -> int , also keine ToHashCode Methode? edit: ToHashCode ist in Ordnung. Siehe die Antwort von @CyrusNajmabadi unten.

@jamesqo StringComparison ist eine Aufzählung.
Die Leute könnten jedoch stattdessen das Äquivalent von StringComparer verwenden.

Können wir zu einer impliziten Konvertierung von HashCode -> int zurückkehren, also keine ToHashCode-Methode?

Wir haben dies diskutiert und uns in der Sitzung dagegen entschieden. Das Problem ist, dass, wenn der Benutzer das letzte "int" erhält, oft zusätzliche Arbeit geleistet wird. dh die Interna des Hashcodes führen oft einen Finalisierungsschritt durch und können sich selbst in einen neuen Zustand zurücksetzen. Das mit einer impliziten Konvertierung passieren zu lassen, wäre seltsam. Wenn Sie dies getan haben:

HashCode hc = ...

int i1 = hc;
int i2 = hc;

Dann kann es zu unterschiedlichen Ergebnissen kommen.

Aus diesem Grund mögen wir auch die explizite Konvertierung nicht (da die Leute Konvertierungen nicht als Änderung des internen Zustands betrachten).

Mit einer Methode können wir explizit dokumentieren, dass dies geschieht. Wir können es möglicherweise sogar benennen, um es so zu vermitteln. zB "ToHashCodeAndReset" (obwohl wir uns dagegen entschieden haben). Aber zumindest kann die Methode eine klare Dokumentation enthalten, die der Benutzer in Dingen wie Intellisense sehen kann. Bei Konvertierungen ist das nicht der Fall.

Ich habe die Byte-Überladungen entfernt, da wir uns mit typeof(T) == typeof(byte) spezialisieren können

IIRC gab es einige Bedenken, dass dies aus der JIT-Perspektive nicht in Ordnung ist. Aber das war möglicherweise nur für die Nicht-Wert-Typ-"typeof()"-Fälle der Fall. Solange der Jit für die Fälle vom Werttyp typeof() effektiv das Richtige tut, sollte das gut sein.

@CyrusNajmabadi Ich war mir nicht bewusst, dass die Konvertierung in ein int einen mutierenden Zustand beinhalten könnte. ToHashCode es dann.

Für diejenigen, die über die Krypto-Perspektive nachdenken - http://tuprints.ulb.tu-darmstadt.de/2094/1/thesis.lehmann.pdf

@terrajobst , hatten Sie Zeit, meine Kommentare (ab hier ) zu lesen und zu entscheiden, ob Sie mit der optimierten API-Form einverstanden sind? Wenn ja, dann denke ich, dass dies als API-genehmigt / zum Greifen nah markiert werden kann und wir uns für einen Hash-Algorithmus entscheiden können.

@blowdart ,

Ich habe es oben vielleicht nicht zu explizit gemacht, aber die einzigen nicht-kryptografischen Hashes, die ich von HashDoS-Einbrüchen nicht kenne, sind Marvin und SipHash. Das heißt, selbst das Seeding (sagen wir) Murmur mit einem zufälligen Wert kann immer noch gebrochen und für ein DoS verwendet werden.

Keine, ich fand es nur interessant, und ich denke, die Dokumentation dazu sollte sagen "Nicht für Hash-Codes geeignet, die über kryptografische Algorithmen generiert werden."

Entscheidungen

  • Wir sollten alle AddRange Methoden entfernen, da das Szenario unklar ist. Es ist eher unwahrscheinlich, dass Arrays sehr oft auftauchen. Und wenn es sich um größere Arrays handelt, stellt sich die Frage, ob die Berechnung zwischengespeichert werden soll. Wenn Sie die for-Schleife auf der aufrufenden Seite sehen, wird deutlich, dass Sie darüber nachdenken müssen.
  • Wir möchten auch keine IEnumerable Überladungen zu AddRange hinzufügen, weil sie allokiert würden.
  • Wir glauben nicht, dass wir die Überladung von Add brauchen, die string und StringComparison . Ja, diese sind wahrscheinlich effizienter als Anrufe über IEqualityComparer , aber wir können dies später beheben.
  • Wir halten es für eine gute Idee, GetHashCode mit einem Fehler als veraltet zu markieren, aber wir gehen noch einen Schritt weiter und verstecken uns auch vor IntelliSense.

Dies lässt uns mit:

```C#
// Wird in der Kernbaugruppe leben
// .NET Framework: mscorlib
// .NET Core: System.Runtime / System.Private.CoreLib
Namensraum-System
{
öffentliche Struktur HashCode
{
public static int Kombinieren(T1-Wert1);
public static int Kombinieren(T1-Wert1, T2-Wert2);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7);
public static int Kombinieren(T1-Wert1, T2-Wert2, T3-Wert3, T4-Wert4, T5-Wert5, T6-Wert6, T7-Wert7, T8-Wert8);

    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();
}

}
```

Nächste Schritte: Das Problem ist greifbar – um die API zu implementieren, die wir mit mehreren Kandidatenalgorithmen als Experimente https://github.com/dotnet/corefx/issues/14354#issuecomment -305028686 für eine Liste, damit wir entscheiden können, welcher Algorithmus verwendet werden soll (basierend auf Durchsatz- und Verteilungsmessungen, wahrscheinlich unterschiedliche Antwort pro CPU-Architektur).

Komplexität: Groß

Wenn jemand daran interessiert ist, es abzuholen, ping uns bitte. Es könnte sogar Platz für mehrere Personen sein, die gemeinsam daran arbeiten. ( @jamesqo Sie haben die Priorität, da Sie am meisten und am längsten in die Ausgabe investiert haben)

@karelz Trotz meines obigen Kommentars habe ich meine Meinung geändert, weil ich glaube nicht, dass ich die Qualifikationen habe, den besten Hash-Algorithmus auszuwählen. Ich habe mir einige der aufgelisteten Bibliotheken @morganbr angesehen und festgestellt, dass die Implementierung ziemlich komplex ist , sodass ich sie nicht einfach in C# übersetzen kann, um sie selbst zu testen. Ich habe wenig Hintergrundwissen in C++, daher würde es mir auch schwer fallen, nur die Bibliothek zu installieren und eine Test-App zu schreiben.

Ich möchte jedoch nicht, dass dies für immer auf der Liste bleibt. Wenn sich ab heute in einer Woche niemand mehr damit beschäftigt, werde ich in Betracht ziehen, eine Frage auf Programmers SE oder Reddit zu stellen.

Ich habe es nicht auf den Prüfstand gestellt (oder anderweitig optimiert), aber hier ist eine grundlegende Implementierung des Murmur3-Hash-Algorithmus, den ich in mehreren meiner persönlichen Projekte verwende: https://gist.github.com/tannergooding/0a12559d1a912068b9aeb4b9586aad7f

Ich denke, die optimale Lösung hier besteht darin, den Hashing-Algorithmus basierend auf der Größe der Eingabedaten dynamisch zu ändern.

Beispiel: Mumur3 (und andere) sind sehr schnell für große Datenmengen und bieten eine große Verteilung, aber sie können bei kleineren Datenmengen "schlecht" (Geschwindigkeit, nicht Verteilung) funktionieren.

Ich stelle mir vor, wir sollten etwas tun wie: Wenn die Gesamtbyteanzahl kleiner als X ist, führen Sie Algorithmus A aus; andernfalls führen Sie Algorithmus B aus. Dieser ist immer noch deterministisch (pro Durchlauf), ermöglicht es uns jedoch, Geschwindigkeit und Verteilung basierend auf der tatsächlichen Größe der Eingabedaten bereitzustellen.

Es ist wahrscheinlich auch erwähnenswert, dass einige der genannten Algorithmen Implementierungen haben, die speziell für SIMD-Befehle entwickelt wurden, so dass eine der leistungsfähigsten Lösungen wahrscheinlich eine FCALL auf einer bestimmten Ebene beinhalten würde (wie bei einigen der BufferCopy-Implementierungen) oder eine Abhängigkeit beinhalten kann auf System.Numerics.Vector .

@jamesqo , wir helfen gerne bei der Auswahl; Was wir am meisten brauchen, sind Leistungsdaten für Kandidatenimplementierungen (idealerweise C#, obwohl @tannergooding betont , dass einige Algorithmen spezielle Compilerunterstützung benötigen). Wie ich oben erwähnt habe, werden wir, wenn Sie einen Kandidaten erstellen, der nicht ausgewählt wurde, wahrscheinlich später verwenden, also machen Sie sich keine Sorgen, dass Arbeit verschwendet wird.

Ich weiß, dass es Benchmarks für verschiedene Implementierungen gibt, aber ich denke, es ist wichtig, einen Vergleich mit dieser API und einem wahrscheinlichen Bereich von Eingaben (zB Strukturen mit 1-10 Feldern) zu haben.

@tannergooding , diese Art von Anpassungsfähigkeit ist möglicherweise am leistungsfähigsten, aber ich sehe nicht, wie sie mit der Add-Methode funktionieren würde, da sie nicht weiß, wie oft sie aufgerufen wird. Obwohl wir dies mit Combine tun könnten, würde dies bedeuten, dass eine Reihe von Add-Aufrufen zu einem anderen Ergebnis führen könnte als der entsprechende Combine-Aufruf.

Da der wahrscheinlichste Eingabebereich 4-32 Bytes beträgt ( Combine`1 - Combine`8 ), gibt es in diesem Bereich hoffentlich keine großen Leistungsänderungen.

diese Art von Anpassungsfähigkeit ist möglicherweise am leistungsfähigsten, aber ich sehe nicht, wie sie mit der Add-Methode funktionieren würde, da sie nicht weiß, wie oft sie aufgerufen wird.

Ich persönlich bin nicht davon überzeugt, dass die API-Form für allgemeines Hashing ganz richtig ist (es ist jedoch naheliegend) ...

Derzeit stellen wir Combine Methoden für die statische Konstruktion zur Verfügung. Wenn diese dazu gedacht sind, alle Eingaben zu kombinieren und einen endgültigen Hash-Code zu erzeugen, dann ist der Name 'schlecht' und etwas wie Compute könnte passender sein.

Wenn wir Combine Methoden verfügbar machen, sollten sie nur alle Eingaben mischen und die Benutzer sollten aufgefordert werden, eine Finalize Methode aufzurufen, die die Ausgabe der letzten Kombination sowie die Gesamtzahl der Bytes, die waren kombiniert, um einen finalisierten Hash-Code zu erzeugen (das Finalisieren eines Hash-Codes ist wichtig, da er die Lawinen der Bits verursacht).

Für das Builder-Muster stellen wir eine Methode Add und ToHashCode bereit. Es ist nicht klar, ob die Methode Add dazu gedacht ist, die Bytes zu speichern und nur beim Aufruf von ToHashCode kombinieren/abzuschließen (in diesem Fall können wir den richtigen Algorithmus dynamisch auswählen) oder ob dies der Fall ist die spontan kombiniert werden sollen, sollte klar sein, dass dies der Fall ist (und dass die Implementierung die Gesamtgröße der kombinierten Bytes intern verfolgen sollte).

Für alle, die einen weniger komplizierten Ausgangspunkt suchen, versuchen Sie es mit xxHash32. Das lässt sich wahrscheinlich ziemlich leicht in C# übersetzen (die Leute haben es getan ).

Ich teste immer noch lokal, aber ich sehe die folgenden Durchsatzraten für meine C#-Implementierung von Murmur3.

Dies sind die statischen Combine-Methoden für 1-8 Eingaben:

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

Meine Implementierung geht davon aus, dass GetHashCode für jede Eingabe aufgerufen werden sollte und dass der berechnete Wert vor der Rückgabe abgeschlossen werden sollte.

Ich habe int Werte kombiniert, da sie am einfachsten zu testen sind.

Um den Durchsatz zu berechnen, habe ich 10.001 Iterationen ausgeführt und die erste Iteration als "Aufwärmlauf" verworfen.

In jeder Iteration führe ich 10.000 Unteriterationen aus, wobei ich HashCode.Combine aufrufe und das Ergebnis der vorherigen Unteriteration als ersten Eingabewert in der nächsten Iteration übergebe.

Ich durchschnittliche dann alle Iterationen, um die durchschnittliche verstrichene Zeit zu erhalten, und dividiere diese weiter durch die Anzahl der Unteriterationen, die pro Schleife ausgeführt werden, um die durchschnittliche Zeit pro Aufruf zu erhalten. Ich berechne dann die Anzahl der Anrufe, die pro Sekunde getätigt werden können, und multipliziere diese mit der Anzahl der kombinierten Bytes, um den tatsächlichen Durchsatz zu berechnen.

Werde den Code bereinigen und in Kürze teilen.

@tannergooding , das klingt nach einem großen Fortschritt. Um sicherzustellen, dass Sie die richtigen Messwerte erhalten, ist die Absicht der API, dass ein Aufruf von HashCode.Combine(a, b) einem Aufruf entspricht

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

In beiden Fällen sollten die Daten in den gleichen internen Hash-Zustand eingespeist und der Hash am Ende einmal finalisiert werden.

👍

Das ist effektiv, was der Code, den ich geschrieben habe, tut. Der einzige Unterschied besteht darin, dass ich den gesamten Code effektiv einfüge (es besteht keine Notwendigkeit, new HashCode() zuzuweisen und die Anzahl der kombinierten Bytes zu verfolgen, da sie konstant ist).

@morganbr. Implementierung + Durchsatztest für Murmur3: https://gist.github.com/tannergooding/89bd72f05ab772bfe5ad3a03d6493650

MurmurHash3 basiert auf dem hier beschriebenen Algorithmus: https://github.com/aappleby/smhasher/wiki/MurmurHash3 , Repo sagt, es sei MIT

Arbeiten an xxHash32 (BSD-2-Klausel -- https://github.com/Cyan4973/xxHash/blob/dev/xxhash.c) und SpookyHash (Public Domain -- http://www.burtleburtle.net/bob/hash /spooky.html) Varianten

@tannergooding Nochmal, kein Hash-Experte, aber ich erinnerte mich, [einen Artikel zu lesen [1], der sagte, Murmur sei nicht DoS-resistent, also habe ich nur darauf hingewiesen, bevor wir uns dafür entscheiden.

@jamesqo , ich könnte mich irren, aber ich bin mir ziemlich sicher, dass die Sicherheitsanfälligkeit auf Murmur2 und nicht auf Murmur3 zutrifft.

In beiden Fällen implementiere ich mehrere Algorithmen, damit wir Durchsatzergebnisse für C# erhalten. Die Verteilung und andere Eigenschaften dieser Algorithmen sind ziemlich bekannt, sodass wir später den besten auswählen können 😄

Hoppla, vergessen, den Artikel zu verlinken: http://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/.

@tannergooding OK. Klingt fair :+1:

@tannergooding , ich habe mir deine Murmur3-Implementierung angesehen und sie sieht im Allgemeinen richtig und wahrscheinlich ziemlich gut optimiert aus. Um sicherzustellen, dass ich es richtig verstehe, verwenden Sie die Tatsache, dass CombinedValue und der interne Zustand von Murmur beide 32 Bit haben? Das ist wahrscheinlich eine ziemlich gute Optimierung für diesen Fall und erklärt einige meiner früheren Verwirrung.

Wenn wir es übernehmen würden, braucht es möglicherweise ein paar Optimierungen (sie werden jedoch wahrscheinlich keinen großen Unterschied bei den Perf-Messungen machen):

  • Kombinierensollte immer noch CombineValue für Wert1 aufrufen
  • Die ersten CombineValue-Aufrufe sollten einen zufälligen Seed annehmen
  • ToHashCode sollte _bytesCombined und _combinedValue zurücksetzen

In der Zwischenzeit, während ich mich nach dieser API sehne, wie schlimm ist es für mich, GetHashCode über (field1, field2, field3).GetHashCode() zu implementieren?

@jnm2 , der ValueTuple-Hash-Code-Combiner neigt dazu, Ihre Eingaben im Hash-Code zu ordnen (und die am wenigsten aktuellen zu verwerfen). Bei einigen Feldern und einer Hash-Tabelle, die durch eine Primzahl dividiert wird, bemerken Sie es möglicherweise nicht. Bei vielen Feldern oder einer Hash-Tabelle, die durch eine Zweierpotenz dividiert wird, hat die Entropie des zuletzt eingefügten Felds den größten Einfluss darauf, ob Kollisionen auftreten (z 'wird wahrscheinlich viele Kollisionen haben, wenn es ein Guid ist, werden Sie wahrscheinlich nicht).

ValueTuple funktioniert auch nicht gut mit Feldern, die alle 0 sind.

Nebenbei bemerkt musste ich die Arbeit an anderen Implementierungen einstellen (mit höherer Priorität arbeiten). Ich bin mir nicht sicher, wann ich es wieder abholen kann.

Wenn das für einen strukturierten Typ nicht gut genug ist, warum ist es dann gut genug für ein Tupel?

@jnm2 , das ist einer der Gründe, warum es sich lohnt, diese Funktion zu entwickeln - damit wir minderwertige Hashes im gesamten Framework ersetzen können.

Große Tabelle von Hashfunktionen mit Leistungs- und Qualitätsmerkmalen:
https://github.com/leo-yuriev/t1ha

@arespr Ich denke, das Team sucht nach einer C#

@tannergooding Können Sie dieses Problem immer noch nicht

edit: Habe einen Beitrag auf Reddit gemacht. https://www.reddit.com/r/csharp/comments/6qsysm/Looking_for_hash_expert_to_help_net_core_team/?ref=share&ref_source=link

@jamesqo , ich habe ein paar Dinge mit höherer Priorität auf meinem Teller und werde in den nächsten 3 Wochen nicht dazu kommen.

Außerdem werden die aktuellen Messungen durch das begrenzt, was wir derzeit in C# codieren können. Wenn dies jedoch zur Sache wird (https://github.com/dotnet/designs/issues/13), werden sich die Messungen wahrscheinlich etwas ändern ;)

Außerdem werden die aktuellen Messungen durch das begrenzt, was wir derzeit in C# codieren können. Wenn dies jedoch zu einer Sache wird (dotnet/designs#13), werden sich die Messungen wahrscheinlich etwas ändern ;)

Das ist in Ordnung – wir können den Hash-Algorithmus jederzeit ändern, sobald intrinsische Elemente verfügbar sind. Das Einkapseln/Randomisieren des Hash-Codes ermöglicht uns dies. Wir suchen nur nach etwas, das in seinem aktuellen Zustand den besten Kompromiss zwischen Leistung und Verteilung für die Laufzeit bietet.

@jamesqo , danke, dass hast , die

Hi! Ich habe mir gerade die Diskussion durchgelesen, und zumindest scheint mir der Fall zugunsten des murmur3-32 PoC entschieden geschlossen zu sein. Das scheint mir übrigens eine sehr gute Wahl zu sein, und ich würde empfehlen, keine unnötige Arbeit mehr aufzuwenden (aber vielleicht sogar die .Add() Mitglieder fallen zu lassen ...).

Aber für den unwahrscheinlichen Fall, dass jemand mit mehr Performance-Arbeiten fortfahren möchte, könnte ich Code für xx32, xx64, hsip13/24, seahash, murmur3-x86/32 (und ich habe das marvin32-Impl von oben integriert) und (noch nicht optimiert) sip13/24, spookyv2. Einige Versionen von City lassen sich bei Bedarf einfach portieren. Dieses halb aufgegebene Projekt hatte einen etwas anderen Anwendungsfall im Sinn, daher gibt es keine HashCode-Klasse mit der vorgeschlagenen API; aber für Benchmarking sollte es nicht viel ausmachen.

Definitiv nicht produktionsreif: Der Code verwendet großzügige Mengen an Brute-Force wie Copy-Pasta, krebsartige Ausbreitung von Aggressiv-Inline und unsicher; Endianness existiert nicht, ebensowenig unausgerichtete Reads. Selbst Tests gegen ref-impl Testvektoren sind beschönigend "unvollständig".

Wenn das überhaupt hilft, sollte ich in den nächsten zwei Wochen genug Zeit finden, um die ungeheuerlichsten Probleme zu beheben und den Code und einige vorläufige Ergebnisse zur Verfügung zu stellen.

@gimpf

Ich habe mir gerade die Diskussion durchgelesen, und zumindest scheint mir der Fall zugunsten des murmur3-32 PoC entschieden geschlossen zu sein. Was mir übrigens eine sehr gute Wahl ist, und ich würde empfehlen, keine unnötige Arbeit mehr aufzuwenden

Nein, die Leute bevorzugen Murmur3 noch nicht. Wir möchten sicherstellen, dass wir den absolut besten Algorithmus in Bezug auf das Gleichgewicht zwischen Leistung und Verteilung auswählen, damit wir nichts unversucht lassen können.

Aber für den unwahrscheinlichen Fall, dass jemand mit mehr Performance-Arbeiten fortfahren möchte, könnte ich Code für xx32, xx64, hsip13/24, seahash, murmur3-x86/32 (und ich habe das marvin32-Impl von oben integriert) und (noch nicht optimiert) sip13/24, spookyv2. Einige Versionen von City lassen sich bei Bedarf einfach portieren.

Ja bitte! Wir wollen Code für so viele Algorithmen wie möglich sammeln, gegen die wir testen können. Jeder neue Algorithmus, den Sie beitragen können, ist wertvoll. Es wäre sehr wünschenswert, wenn Sie auch die City-Algorithmen portieren könnten.

Definitiv nicht produktionsreif: Der Code verwendet großzügige Mengen an Brute-Force wie Copy-Pasta, krebsartige Ausbreitung von Aggressiv-Inline und unsicher; Endianness existiert nicht, ebensowenig unausgerichtete Reads. Selbst Tests gegen ref-impl Testvektoren sind beschönigend "unvollständig".

Das ist ok. Bringen Sie einfach den Code ein, und jemand anderes kann ihn bei Bedarf finden.

Wenn das überhaupt hilft, sollte ich in den nächsten zwei Wochen genug Zeit finden, um die ungeheuerlichsten Probleme zu beheben und den Code und einige vorläufige Ergebnisse zur Verfügung zu stellen.

Ja das wäre toll!

@jamesqo Ok, ich werde eine Notiz

@gimpf das hört sich wirklich toll an und wir würden gerne von deinen Fortschritten hören (kein Warten, bis du jeden Algorithmus

Ich habe keine Analyse gesehen, wie die Entropie von Seahash im Vergleich zu anderen Algorithmen abschneidet. Haben Sie dazu Hinweise? Es hat interessant klingende Kompromisse bei der Leistungsfähigkeit ... Vektorisierung klingt schnell, aber modulare Arithmetik klingt langsam.

@morganbr Ich habe einen Teaser parat.

Über SeaHash : Nein, ich weiß noch nichts über die Qualität; Falls die Leistung interessant ist, würde ich sie zu SMasher hinzufügen. Zumindest behauptet der Autor, dass es gut ist (verwendet es für Prüfsummen in einem Dateisystem) und behauptet auch, dass beim Mischen keine Entropie weggeworfen wird.

Zu den Hashes und Benchmarks : Projekt Haschisch.Kastriert , Wiki-Seite mit ersten Benchmarking-Ergebnissen im Vergleich von xx32, xx64, hsip13, hsip24, marvin32, sea und murmur3-32.

Einige wichtige Vorbehalte:

  • Dies war ein sehr schneller Bench-Lauf mit niedrigen Genauigkeitseinstellungen.
  • Die Implementierungen sind noch nicht wirklich fertig und einige Anwärter fehlen noch. Die Streaming-Implementierungen (so etwas würde für eine sinnvolle .Add()-Unterstützung notwendig werden) bedürfen aktueller Optimierung.
  • SeaHash verwendet derzeit keinen Seed.

Erste Eindrücke:

  • für große Nachrichten ist xx64 die schnellste der aufgeführten Implementierungen (ungefähr 3,25 Byte pro Zyklus, soweit ich weiß, oder 9,5 GiB/s auf meinem Notebook)
  • für kurze Nachrichten ist nichts großartig, aber murmur3-32 und (überraschend) Seahash haben einen Vorteil, aber letzteres wird wahrscheinlich dadurch erklärt, dass Seahash noch keinen Seed verwendet.
  • der "Benchmark" für den Zugriff auf ein HashSet<> muss bearbeitet werden, da alles fast innerhalb des Messfehlers liegt (ich habe größere Unterschiede gesehen, aber immer noch nicht der Rede wert)
  • Beim Kombinieren von Hash-Codes ist der murmur-3A-PoC etwa 5- bis 20-mal schneller als das, was wir hier haben
  • einige Abstraktionen in C# sind sehr teuer; das macht den Vergleich von Hash-Algorithmen ärgerlicher als nötig.

Ich schreibe dir nochmal, sobald ich die Situation etwas verbessert habe.

@gimpf , das ist ein fantastischer Start! Ich habe mir den Code und die Ergebnisse angesehen und habe ein paar Fragen.

  1. Ihre Ergebnisse zeigen, dass SimpleMultiplyAdd etwa 5x langsamer ist als Murmur3a von @tannergooding. Das erscheint seltsam, da Murmur mehr Arbeit zu erledigen hat als Multiplizieren + Addieren (obwohl ich zugeben muss, dass Rotieren eine schnellere Operation ist als Addieren). Ist es möglich, dass Ihre Implementierungen eine gemeinsame Ineffizienz aufweisen, die nicht in dieser Murmur-Implementierung vorhanden ist, oder sollte ich dies als benutzerdefinierte Implementierungen lesen, die einen großen Vorteil gegenüber Allzweckimplementierungen haben?
  2. Es ist gut, Ergebnisse für 1, 2 und 4 Kombinationen zu haben, aber diese API geht bis zu 8. Wäre es möglich, auch dafür Ergebnisse zu erhalten, oder verursacht das zu viele Duplizierungen?
  3. Ich habe gesehen, dass Sie auf X64 ausgeführt wurden, daher sollten uns diese Ergebnisse bei der Auswahl unseres X64-Algorithmus helfen, aber andere Benchmarks deuten darauf hin, dass sich die Algorithmen zwischen X86 und X64 ziemlich stark unterscheiden können. Fällt es Ihnen leicht, auch X86-Ergebnisse zu erhalten? (Irgendwann müssten wir auch ARM und ARM64 bekommen, aber die können definitiv warten)

Ihre HashSet-Ergebnisse sind besonders interessant. Wenn sie sich halten, ist dies ein möglicher Fall dafür, eine bessere Entropie einer schnelleren Hash-Zeit vorzuziehen.

@morganbr Dieses Wochenende war eher

Zu Ihren Fragen:

  1. Ihre Ergebnisse zeigen, dass SimpleMultiplyAdd etwa 5x langsamer ist als Murmur3a von @tannergooding. Das scheint seltsam...

Ich habe mich selbst gewundert. Das war ein Kopier-/Einfügefehler, SimpleMultiplyAdd kombinierte immer vier Werte... Außerdem wurde der Multiply-Add-Combiner durch die Neuordnung einiger Anweisungen etwas schneller (~60% höherer Durchsatz).

Ist es möglich, dass Ihre Implementierungen eine gemeinsame Ineffizienz aufweisen, die nicht in dieser Murmur-Implementierung vorhanden ist, oder sollte ich dies als benutzerdefinierte Implementierungen lesen, die einen großen Vorteil gegenüber Allzweckimplementierungen haben?

Ich vermisse wahrscheinlich einige Dinge, aber es scheint, dass für .NET allgemeine Implementierungen für diesen Anwendungsfall nicht verwendbar sind. Ich habe Methoden im Combine-Stil für alle Algorithmen geschrieben, und die meisten Algorithmen mit Hash-Code-Kombinationen sind _viel_ besser als die Allzweck-Methoden.

Allerdings bleiben selbst diese Implementierungen zu langsam; weitere Arbeiten sind erforderlich. .NET-Performance in diesem Bereich ist für mich absolut undurchsichtig; Das Hinzufügen oder Entfernen einer Kopie einer lokalen Variablen kann die Leistung leicht um den Faktor zwei ändern. Ich werde wahrscheinlich nicht in der Lage sein, Implementierungen bereitzustellen, die ausreichend gut optimiert sind, um die beste Option auszuwählen.

  1. Es ist gut, Ergebnisse für 1, 2 und 4 Kombinationen zu haben, aber diese API geht bis zu 8.

Ich habe die Mähdrescher-Benchmarks erweitert. Keine Überraschungen an dieser Front.

  1. Ich habe gesehen, dass Sie auf X64 laufen (...). Ist es für Sie einfach, auch X86-Ergebnisse zu erhalten?

Es war einmal, aber dann habe ich auf .NET Standard portiert. Jetzt bin ich in der Abhängigkeits-Hölle und nur .NET Core 2 und CLR 64-Bit-Benchmarks funktionieren. Dies kann leicht genug gelöst werden, sobald ich die aktuellen Probleme gelöst habe.

Glaubst du, das wird es in der Version 2.1 schaffen?

@gimpf Sie haben eine Weile nichts gepostet - haben Sie ein Fortschrittsupdate zu Ihren Implementierungen? :smiley:

@jamesqo Ich habe einige Benchmarks behoben, die zu seltsamen Ergebnissen geführt haben, und City32, SpookyV2, Sip13 und Sip24 zur Liste der verfügbaren Algorithmen hinzugefügt. Die Sips sind erwartungsgemäß schnell (relativ zum Durchsatz von xx64), City und Spooky sind es nicht (das gleiche gilt immer noch für SeaHash).

Für die Kombination von Hash-Codes sieht Murmur3-32 immer noch wie eine gute Wahl aus, aber ich muss noch einen umfassenderen Vergleich durchführen.

Außerdem hat die Streaming-API (.Add()) den unglücklichen Nebeneffekt, dass einige Hash-Algorithmen von der Kandidatenliste entfernt werden. Da auch die Leistungsfähigkeit einer solchen API fraglich ist, sollten Sie überdenken, ob Sie sie von Anfang an anbieten.

Wenn der .Add() Teil vermieden würde und der Hash-Combiner einen Seed verwendet, würde es meiner Meinung nach nicht schaden, den Combiner von tg zu bereinigen und eine kleine Testsuite zu erstellen, und nennen es einen Tag. Da ich jedes Wochenende nur wenige Stunden zur Verfügung habe und die Leistungsoptimierung etwas mühsam ist, könnte sich das Anfertigen der vergoldeten Version etwas in die Länge ziehen...

@gimpf , das klingt nach einem tollen Fortschritt. Haben Sie eine Ergebnistabelle zur Hand, damit wir sehen können, ob es genug gibt, um eine Entscheidung zu treffen und voranzukommen?

@morganbr Ich habe meine Benchmarking-Ergebnisse aktualisiert.

Im Moment habe ich nur 64-Bit-Ergebnisse für .NET Core 2. Für diese Plattform ist City64 ohne Seed über alle Größen hinweg am schnellsten. XX-32 enthält einen Samen und ist mit Murmur-3-32 verbunden. Glücklicherweise sind dies dieselben Algorithmen, die für 32-Bit-Plattformen den Ruf haben, schnell zu sein, aber wir müssen natürlich überprüfen, dass dies auch für meine Implementierung gilt. Die Ergebnisse scheinen für die reale Leistung repräsentativ zu sein, außer dass Sea und SpookyV2 ungewöhnlich langsam erscheinen.

Sie müssen sich überlegen, wie viel Hash-Dos-Schutz für Hash-Code-Kombinierer wirklich benötigt wird. Wenn das Seeding nur erforderlich ist, um den Hash für die Persistenz offensichtlich unbrauchbar zu machen, wäre eine XOR-Verknüpfung von city64 mit einem 32-Bit-Seed eine Verbesserung. Da dieses Dienstprogramm nur dazu da ist, Hashes zu kombinieren (und nicht beispielsweise den Hash-Code für Strings ersetzen oder ein Drop-In-Hasher für Integer-Arrays usw. sein), könnte das gut genug sein.

Wenn Sie OTOH denken, dass Sie es brauchen, werden Sie froh sein zu sehen, dass Sip13 normalerweise weniger als 50% langsamer ist als XX-32 (auf 64-Bit-Plattformen), aber dieses Ergebnis wird wahrscheinlich für 32-Bit-Apps erheblich anders sein.

Ich weiß nicht, wie sehr es für Corefx relevant ist, aber ich habe LegacyJit 32-Bit-Ergebnisse (mit FW 4.7) hinzugefügt.

Ich möchte sagen, dass die Ergebnisse lächerlich langsam sind. Aber als Beispiel, bei 56 MiB/s vs. 319 MiB/s lache ich nicht (das ist Sip, es fehlt am meisten die Rotations-Links-Optimierung). Ich glaube, ich erinnere mich, warum ich mein .NET-Hash-Algorithmus-Projekt im Januar abgebrochen habe...

RyuJit-32bit fehlt also immer noch und wird (hoffentlich) ganz andere Ergebnisse liefern, aber für LegacyJit-x86 gewinnt Murmur-3-32 praktisch, und nur City-32 und xx-32 können nahe kommen. Murmur hat mit nur etwa 0,4 bis 1,1 GB/s statt 0,6 bis 2 GB/s (auf dem gleichen Rechner) immer noch eine schlechte Leistung, aber immerhin liegt es im richtigen Rahmen.

Ich werde heute Abend die Benchmarks auf einigen meiner Boxen durchführen und Ergebnisse veröffentlichen (Ryzen, i7, Xeon, A10, i7 Mobile und ich denke, ein paar andere).

@tannergooding @morganbr Einige nette und einige wichtige Updates.

Wichtig zuerst:

  • Ich habe einige Combine-Implementierungen behoben, die falsche Hash-Werte erzeugten.
  • Die Benchmark-Suite arbeitet jetzt härter, um ein ständiges Falten zu vermeiden. City64 war anfällig (wie in der Vergangenheit Murmur-3-32). Bedeutet nicht, dass ich jetzt jedes Ergebnis verstehe, aber sie sind viel plausibler.

Schöne Dinge:

  • Combiner-Implementierungen sind jetzt für alle 1 bis 8 Argumentüberladungen verfügbar, einschließlich der etwas umständlicheren manuell ausgerollten Implementierungen für xx/city.
  • Tests und Benchmarks überprüfen auch diese. Da viele Hash-Algorithmen spezielle Low-Byte-Nachrichten haben, könnten diese Messungen von Interesse sein.
  • Vereinfachte laufende Benchmarks für mehrere Ziele (Core vs. FW).

So führen Sie eine Suite auf allen Prime-Implementierungen zum Kombinieren von Hash-Codes aus, einschließlich "Empty" (reiner Overhead) und "Multiply-Add" (geschwindigkeitsoptimierte Version der berühmten SO-Antwort):

bin\Release\net47\Haschisch.Benchmarks.Net47.exe -j:clr_x86 -j:clr_x64_legacy -j:clr_x64 -j:core_x64 -- CombineHashCode --allcategories=prime

(_Das Ausführen von 32-Bit-Core-Benchmarks scheint praktischerweise eine Vorabversion von BenchmarkDotNet zu erfordern (oder vielleicht ein Nur-32-Bit-Setup plus den Core-basierten Bench-Runner). Es sollte dann mit -j:core_x86 funktionieren, hoffentlich)_

Ergebnisse : Nach allen Fehlerbehebungen scheint xx32 bei allen Überlastungen mit 64-Bit-RyuJIT unter Windows 10 auf einem mobilen Haswell i7 in einem "schnellen" Durchlauf zu gewinnen. Zwischen den Sips und marvin32 gewinnt immer Sip-1-3. Sip-1-3 ist etwa 4-mal langsamer als xx32, was wiederum etwa 2-mal langsamer ist als ein primitiver Multiply-Add-Combiner. 32-Bit-Core-Ergebnisse fehlen noch, aber ich warte mehr oder weniger auf eine stabile BenchmarkDotNet-Version, die dieses Problem für mich löst.

(Bearbeiten) Ich habe gerade einen Schnelldurchlauf eines Benchmarks für den Zugriff auf ein Hash-Set hinzugefügt. Dies hängt offensichtlich viel mehr von Details ab als die obigen µ-Benchmarks, aber Sie sollten es sich vielleicht ansehen.

Nochmals vielen Dank

Zunächst würde ich die Algorithmen wie folgt aufteilen:
Fast+Good Entropie (geordnet nach Geschwindigkeit):

  1. xxHash32
  2. City64 (Dies wird wahrscheinlich auf x86 langsam sein, daher müssen wir wahrscheinlich etwas anderes für x86 auswählen)
  3. Murmur3A

HashDoS-resistent:

  • Marvin32
  • SIPHash. Wenn wir uns dazu neigen, müssen wir es von den Krypto-Experten von Microsoft überprüfen lassen, um zu bestätigen, dass die Forschungsergebnisse akzeptabel sind. Wir müssen auch herausfinden, welche Parameter sicher genug sind. Das Papier schlägt irgendwo zwischen Sip-2-4 und Sip-4-8 vor.

Außer Konkurrenz (langsam):

  • SpookyV2
  • Stadt32
  • xxHash64
    *SeaHash (und wir haben keine Daten zur Entropie)

Außer Konkurrenz (schlechte Entropie):

  • MultiplizierenAdd
  • HSip

Bevor wir einen Gewinner auswählen, möchte ich sicherstellen, dass andere Leute mit meiner obigen Einteilung einverstanden sind. Wenn es hält, denke ich, müssen wir nur entscheiden, ob wir 2x für HashDoS-Resistenz bezahlen und dann nach Geschwindigkeit gehen.

@morganbr Ihre Gruppierung scheint in Ordnung zu sein. Als Datenpunkt in SipHash-Runden fragte das Rust-Projekt Jean-Philippe Aumasson , der sip-hash w/DJB verfasst hat. Nach dieser Diskussion entschieden sie sich für sip-1-3 für Hash-Tabellen.

(Siehe PR rust:#33940 und die begleitende Ausgabe rust:#29754 ).

Basierend auf den Daten und Kommentaren möchte ich vorschlagen, dass wir xxHash32 auf allen Architekturen verwenden. Der nächste Schritt ist die Implementierung. @gimpf , hast du erstellen ?

Für diejenigen, die sich über HashDoS Sorgen machen, werde ich demnächst einen Vorschlag für eine Allzweck-Hashing-API machen, die Marvin32 enthalten sollte und SipHash enthalten kann. Das wird auch ein geeigneter Ort für die anderen Implementierungen sein, an denen @gimpf und @tannergooding gearbeitet haben.

@morganbr Ich kann eine PR zusammenstellen, wenn es die Zeit erlaubt. Außerdem würde ich persönlich auch xx32 bevorzugen, solange es die Akzeptanz nicht verringert.

@gimpf , wie sieht deine Zeit aus? Wenn Sie nicht wirklich Zeit haben, können wir auch sehen, ob jemand anderes es ausprobieren möchte.

@morganbr Ich hatte geplant, es bis zum 5. November zu machen, und es sieht immer noch gut aus, dass ich die Zeit in den nächsten zwei Wochen finde.

@gimpf , hört sich

@terrajobst - Ich bin etwas spät

```c#
öffentlicher HashCode hinzufügen(T-Wert);
öffentlicher HashCode hinzufügen(T-Wert, IEqualityComparerVergleich);

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();

Genau dasselbe kann jedoch wie folgt erreicht werden, wenn auch mit einer weniger verschwenderischen Array-Zuweisung:

c# public override int GetHashCode() => new HashCode().Add(Name).Add(Surname).Add(Age).ToHashCode();

Beachten Sie, dass Typen auch gemischt werden können. Dies könnte offensichtlich dadurch erreicht werden, dass es innerhalb einer regulären Methode nicht fließend aufgerufen wird. Angesichts dieses Arguments, dass die fließende Schnittstelle nicht unbedingt erforderlich ist, warum gibt es dann die verschwenderische params Überladung überhaupt? Wenn dieser Vorschlag ein schlechter Vorschlag ist, fällt die Überladung von params auf dieselbe Axt. Das und das Erzwingen einer regulären Methode für einen trivialen, aber optimalen Hashcode scheint eine Menge Zeremonie zu sein.

Edit: Ein implicit operator int wäre auch schön für DRY, aber nicht unbedingt entscheidend.

@jcdickinson

können wir den Rückgabetyp der Add-Methode nicht ändern?

Das haben wir bereits im alten Vorschlag besprochen, und es wurde abgelehnt.

warum gibt es die verschwenderische params-Überladung überhaupt?

Wir fügen keine Params-Überladungen hinzu? Führen Sie auf dieser Webseite Strg+F für "params" aus, und Sie werden sehen, dass Ihr Kommentar die einzige Stelle ist, an der dieses Wort auftaucht.

Ein impliziter Operator int wäre auch schön für DRY, aber nicht unbedingt entscheidend.

Ich glaube das wurde oben auch schon besprochen...

@jamesqo danke für die Erklärung.

Parameterüberladungen

Ich meinte AddRange , aber ich denke, da wird es keine Anziehungskraft geben.

@jcdickinson AddRange war im ursprünglichen Vorschlag enthalten, aber nicht in der aktuellen Version. Es wurde von der API-Überprüfung abgelehnt (siehe https://github.com/dotnet/corefx/issues/14354#issuecomment-308190321 von @terrajobst):

Wir sollten alle AddRange Methoden entfernen, da das Szenario unklar ist. Es ist eher unwahrscheinlich, dass Arrays sehr oft angezeigt werden. Und wenn es sich um größere Arrays handelt, stellt sich die Frage, ob die Berechnung zwischengespeichert werden soll. Wenn Sie die for-Schleife auf der aufrufenden Seite sehen, wird deutlich, dass Sie darüber nachdenken müssen.

@gimpf Ich habe den Vorschlag mit xxHash32 mehrfach gefüllt . Fühlen Sie sich frei, diese Implementierung zu greifen. Es hat Tests gegen tatsächliche xxHash32-Vektoren.

Bearbeiten

Apropos Schnittstelle. Ich bin mir vollkommen bewusst, dass ich aus einem Maulwurfshügel einen Berg mache - ignoriere es einfach. Ich verwende den aktuellen Vorschlag gegen echtes Zeug und es gibt viele nervige Wiederholungen.

Ich habe mit der Benutzeroberfläche herumgespielt und verstehe jetzt, warum die flüssige Benutzeroberfläche abgelehnt wurde; es ist deutlich langsamer.

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

Verwenden einer nicht eingebetteten Methode als Hashcodequelle; 50 Aufrufe von Add im Vergleich zu einer fließenden Erweiterungsmethode:

| Methode | Mittelwert | Fehler | StdDev | Skaliert |
|------- |---------:|---------:|---------:|-------: |
| Hinzufügen | 401,6 ns | 1,262 ns | 1,180 ns | 1,00 |
| Zählung | 747,8 ns | 2.329 ns | 2,178 ns | 1,86 |

Das folgende Muster funktioniert jedoch:

```c#
öffentliche Struktur HashCode : System.Collections.IEnumerable
{
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Diese Methode wird für die Syntax des Sammlungsinitialisierers bereitgestellt.", error: true)]
public IEnumerator GetEnumerator() => throw new NotImplementedException();
}

public override int GetHashCode() => new HashCode()
{
    Age, // int
    { Name, StringComparer.Ordinal }, // use Comparer
    Hat // some arbitrary object
}.ToHashCode();

```

Es hat auch identische Leistungsmerkmale wie der aktuelle Vorschlag:

| Methode | Mittelwert | Fehler | StdDev | Skaliert |
|------------ |--------:|--------:|---------:|----- ----:|
| Hinzufügen | 405,0 ns | 2.130 ns | 1,889 ns | 1,00 |
| Initialisierer | 400,8 ns | 4,821 ns | 4.274 ns | 0,99 |

Leider ist es ein bisschen ein Hack, da IEnumerable implementiert werden muss, um den Compiler glücklich zu machen. Davon abgesehen wird das Obsolete sogar bei foreach Obsolete einen Fehler foreach - Sie müssten wirklich Dinge unterbrechen wollen, um auf die Ausnahme zu stoßen. Die MSIL ist bei beiden im Wesentlichen identisch.

@jcdickinson danke für das greifen des Problems. Ich habe Ihnen eine Mitarbeiter-Einladung gesendet, lassen Sie es mich wissen, wenn Sie annehmen, und ich kann Ihnen dieses Problem zuweisen (in der Zwischenzeit mir selbst zuordnen).

Profi-Tipp: Sobald Sie zustimmen, wird GitHub Sie automatisch für alle Benachrichtigungen aus dem Repo anmelden (500+ pro Tag). du abonniert hast.

@jcdickinson , ich bin auf jeden Fall daran interessiert, lästige Wiederholungen zu vermeiden (obwohl ich keine Ahnung habe, wie die Leute über die Initialisierungssyntax denken würden). Ich scheine mich zu erinnern, dass es zwei Probleme mit dem Fließen gab:

  1. Das Perf-Problem, das Sie bemerkt haben
  2. Der Rückgabewert von fließenden Methoden ist eine Kopie der Struktur. Es ist zu leicht, versehentlich Eingaben zu verlieren, wenn Sie Dinge tun wie:
var hc = new HashCode();
var newHc = hc.Add(foo);
hc.Add(bar);
return newHc.ToHashCode();

Da der Vorschlag für diesen Thread bereits genehmigt wurde (und Sie auf dem besten Weg sind, ihn zusammenzuführen), würde ich vorschlagen, für alle Änderungen einen neuen API-Vorschlag zu erstellen.

@karelz Ich glaube, @gimpf hat sich dieses Thema schon vorher bitte stattdessen edit: nvm)

@terrajobst Eine Art Last-Minute-API-Anfrage dafür. Da wir GetHashCode veraltet markiert haben, teilen wir dem Benutzer implizit mit, dass HashCode s keine Werte sind, die verglichen werden sollen, obwohl es sich um Strukturen handelt, die normalerweise unveränderlich/vergleichbar sind. Sollten wir in diesem Fall auch Equals veraltet markieren?

[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.");

Ich denke, etwas Ähnliches wurde mit Span .

Wenn das akzeptiert wird, dann denke ich...

  1. Ich würde in Betracht ziehen, should not oder may not anstelle von cannot in der Obsolete-Nachricht zu verwenden.
  2. Vorausgesetzt, die Ausnahme bleibt bestehen, würde ich dieselbe Zeichenfolge in ihre Nachricht einfügen, nur für den Fall, dass die Methode durch eine Umwandlung oder ein offenes Generic aufgerufen wird.

@ Joe4evr Gut mit mir; Ich habe den Kommentar aktualisiert. Es kann auch von Vorteil sein, dieselbe Nachricht auch in die Ausnahme GetHashCode , dann:

public override int GetHashCode() => throw new NotSupportedException("HashCode is a mutable struct and should not be compared with other HashCodes.");

@morganbr Warum hast du das wieder geöffnet?

Die PR, um es in CoreFX zu veröffentlichen, ist noch nicht abgeschlossen.

@gimpf Haben Sie den Code, den Sie Benchmarking durchgeführt haben, zur Verfügung und/oder könnten Sie schnell sehen, wie das SpookilySharp-Nuget-Paket abschneidet. Ich möchte dieses Projekt nach ein paar Jahren Stagnation abstauben und bin gespannt, wie es sich behauptet.

@JonHanna Er hat es hier gepostet: https://github.com/gimpf/Haschisch.Kastriert

@JonHanna , es würde mich interessieren, wie Ihre Tests verlaufen, damit wir darüber nachdenken können, was in einer universellen nicht-kryptografischen Hashing-API nützlich wäre.

@morganbr Wo wäre ein geeignetes Forum, um eine solche API zu diskutieren? Ich gehe davon aus, dass eine solche API aus mehr als nur dem kleinsten gemeinsamen Nenner bestehen würde, und vielleicht braucht eine gute API auch eine verbesserte JIT bezüglich der Handhabung größerer Strukturen. Was besser gemacht werden könnte, besprechen wir in einer separaten Ausgabe...

@gimpf Hat eine für dich

@morganbr - Können wir den Paketnamen und die Versionsnummer

@karelz , könnten Sie @smitpatel mit Paket-/Versionsinformationen helfen?

Ich würde den täglichen Build von .NET Core ausprobieren - ich würde bis morgen warten.
Ich glaube nicht, dass es ein Paket gibt, von dem man einfach abhängig sein kann.

Frage an die Teilnehmer hier. Die Roslyn-IDE ermöglicht es Benutzern, eine GetHashCode-Impl basierend auf einer Reihe von Feldern/Eigenschaften in ihrer Klasse/Struktur zu generieren. Im Idealfall könnten die Leute das neue HashCode.Combine verwenden, das in https://github.com/dotnet/corefx/pull/25013 hinzugefügt wurde. Einige Benutzer haben jedoch keinen Zugriff auf diesen Code. Wir möchten also immer noch in der Lage sein, einen GetHashCode zu generieren, der für sie funktioniert.

Kürzlich ist uns aufgefallen, dass das von uns generierte Formular problematisch ist. Nämlich, weil VB standardmäßig mit aktivierten Überlaufprüfungen kompiliert und unser Impl Überläufe verursacht. Außerdem hat VB keine Möglichkeit, Überlaufprüfungen für eine Coderegion zu deaktivieren. Sie ist für die gesamte Baugruppe entweder ein- oder ausgeschaltet.

Aus diesem Grund würde ich gerne das von uns bereitgestellte Impl durch ein Formular ersetzen, das nicht unter diesen Problemen leidet. Idealerweise hätte das generierte Formular die folgenden Eigenschaften:

  1. Ein/zwei Zeilen in GetHashCode pro verwendetem Feld/Eigenschaft.
  2. Kein Überlaufen.
  3. Ziemlich gutes Hashing. Wir erwarten keine erstaunlichen Ergebnisse. Aber etwas, das hoffentlich schon anständig ist und nicht die Probleme hat, die man normalerweise mit a + b + c + d oder a ^ b ^ c ^ d bekommt.
  4. Keine zusätzlichen Abhängigkeiten/Anforderungen an den Code.

Eine Option für VB wäre beispielsweise, Folgendes zu generieren:

return (a, b, c, d).GetHashCode()

Dies hängt jedoch davon ab, ob ein Verweis auf System.ValueTuple vorhanden ist. Im Idealfall könnten wir ein Impl haben, das auch ohne dieses funktioniert.

Kennt jemand einen anständigen Hashing-Algorithmus, der mit diesen Einschränkungen arbeiten kann? Vielen Dank!

--

Hinweis: Unser vorhandener ausgegebener Code ist:

        Dim hashCode = -252780983
        hashCode = hashCode * -1521134295 + i.GetHashCode()
        hashCode = hashCode * -1521134295 + j.GetHashCode()
        Return hashCode

Dies kann eindeutig überlaufen.

Dies ist auch für C# kein Problem, da wir einfach unchecked { } um diesen Code herum hinzufügen können. Diese feinkörnige Steuerung ist in VB nicht möglich.

Kennt jemand einen anständigen Hashing-Algorithmus, der mit diesen Einschränkungen arbeiten kann? Vielen Dank!

Nun, Sie könnten Tuple.Create(...).GetHashCode() tun. Offensichtlich zieht dies Zuweisungen nach sich, aber es scheint besser zu sein, als eine Ausnahme auszulösen.

Gibt es einen Grund, warum Sie dem Benutzer nicht einfach sagen können, dass er System.ValueTuple installieren soll? Da es sich um eine eingebaute Sprachfunktion handelt, bin ich mir sicher, dass das System.ValueTuple-Paket mit praktisch allen Plattformen sehr kompatibel ist, oder?

Offensichtlich zieht dies Zuweisungen nach sich, aber es scheint besser zu sein, als eine Ausnahme auszulösen.

Jawohl. Es wäre schön, wenn es keine Zuordnungen verursachen würde.

Gibt es einen Grund, warum Sie dem Benutzer nicht einfach sagen können, dass er System.ValueTuple installieren soll?

Das wäre das Verhalten, wenn wir den ValueTuple-Ansatz generieren. Aber auch hier wäre es schön, wenn wir einfach etwas Gutes generieren könnten, das zu der Art und Weise passt, wie der Benutzer seinen Code derzeit strukturiert hat, ohne ihn dazu zu bringen, seine Struktur schwergewichtig zu ändern.

Es scheint wirklich so, als ob VB-Benutzer eine Möglichkeit haben sollten, dieses Problem auf vernünftige Weise anzugehen :) Aber ein solcher Ansatz entzieht sich mir :)

@CyrusNajmabadi , Wenn Sie wirklich Ihre eigene Hash-Berechnung im Code des Benutzers durchführen müssen, könnte CRC32 funktionieren, da es eine Kombination aus Tabellensuchen und XORs ist (aber keine Arithmetik, die überlaufen kann). Es gibt jedoch einige Nachteile:

  1. CRC32 hat keine große Entropie (aber es ist wahrscheinlich immer noch besser als das, was Roslyn jetzt aussendet).
  2. Sie müssten irgendwo im Code eine Lookup-Tabelle mit 256 Einträgen einfügen oder Code ausgeben, um die Lookup-Tabelle zu generieren.

Wenn Sie es noch nicht tun, hoffe ich, dass Sie den HashCode-Typ erkennen und ihn nach Möglichkeit verwenden können, da XXHash viel besser sein sollte.

@morganbr Siehe https://github.com/dotnet/roslyn/pull/24161

Wir machen folgendes:

  1. Verwenden Sie System.HashCode, falls verfügbar. Fertig.
  2. Andernfalls, wenn in C#:
    2a. Falls nicht im Checked-Modus: Generiere ungerollten Hash.
    2b. Im Checked-Modus: Generiere einen ungerollten Hash, eingehüllt in 'unchecked{}'.
  3. Andernfalls, wenn in VB:
    3b. Falls nicht im Checked-Modus: Generiere ungerollten Hash.
    3c. Im aktivierten Modus, aber Zugriff auf System.ValueTuple: Generiere Return (a, b, c, ...).GetHashCode()
    3d. Im aktivierten Modus ohne Zugriff auf System.ValueTuple. Generieren Sie ungerollten Hash, aber fügen Sie einen Kommentar in VB hinzu, dass Überläufe sehr wahrscheinlich sind.

Es ist '3d', das ist wirklich schade. Grundsätzlich kann jemand, der VB verwendet, aber kein ValueTuple oder ein aktuelles System verwendet, uns nicht verwenden, um einen vernünftigen Hash-Algorithmus für ihn zu generieren.

Sie müssten irgendwo im Code eine Lookup-Tabelle mit 256 Einträgen einfügen

Das wäre völlig ungenießbar :)

Ist Code zur Tabellengenerierung auch ungenießbar? Zumindest nach Wikipedia-Beispiel ist es nicht viel Code (aber es muss immer noch irgendwo in der Quelle des Benutzers stehen).

Wie schrecklich wäre es, die HashCode-Quelle zum Projekt hinzuzufügen, wie es Roslyn (mit IL) mit (den viel einfacheren) Compiler-Attributklassendefinitionen tut, wenn sie nicht über eine referenzierte Assembly verfügbar sind?

Wie schrecklich wäre es, die HashCode-Quelle dem Projekt hinzuzufügen, wie es Roslyn mit (den viel einfacheren) Compiler-Attributklassendefinitionen tut, wenn sie nicht über eine referenzierte Assembly verfügbar sind?

  1. Benötigt die HashCode-Quelle kein Überlaufverhalten?
  2. Ich habe die HashCode-Quelle überflogen. Es ist nicht trivial. All diese Goop in das Projekt des Benutzers zu generieren, wäre ziemlich schwer.

Ich bin nur überrascht, dass es keine guten Möglichkeiten gibt, die Überlaufmathematik in VB zum Laufen zu bringen :(

Selbst wenn wir also zwei Werte miteinander hashen würden, scheint es, als müssten wir zumindest Folgendes erstellen:

```c#
var hc1 = (uint) (Wert1?.GetHashCode() ?? 0); // kann überlaufen
var hc2 = (uint)(value2?.GetHashCode() ?? 0); // kann überlaufen

        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;
        }

ebenso wie QueueRound:

c# private static uint QueueRound(uint hash, uint queuedValue) { hash += queuedValue * Prime3; return Rol(hash, 17) * Prime4; }

Also ich sehe ehrlich gesagt nicht, wie das funktionieren soll :(

Wie schrecklich wäre es, die HashCode-Quelle dem Projekt hinzuzufügen, wie es Roslyn (mit IL) mit (den viel

Wie stellen Sie sich das Funktionieren vor? Was würden die Kunden schreiben und was würden die Compiler dann tun?

Außerdem würde dies alles angehen, wenn .Net bereits öffentliche Helfer auf der Oberflächen-API enthält, die ohne Überlauf von uint in int32 (und umgekehrt) konvertieren.

Existieren die? Wenn ja, kann ich leicht die VB-Versionen schreiben und diese einfach für die Situationen verwenden, in denen wir zwischen den Typen wechseln müssen, ohne überzulaufen.

Ist Code zur Tabellengenerierung auch ungenießbar?

Ich würde so denken. Ich meine, denk mal aus der Kundenperspektive. Sie wollen nur eine anständige GetHashCode-Methode, die schön in sich geschlossen ist und vernünftige Ergebnisse liefert. Es wird ziemlich unangenehm sein, diese Funktion zu verwenden und ihren Code mit Hilfsmüll aufzublähen. Es ist auch ziemlich schlecht, wenn man bedenkt, dass die C#-Erfahrung in Ordnung sein wird.

Sie können möglicherweise ungefähr das richtige Überlaufverhalten erzielen, indem Sie in und aus einer Kombination von 64-Bit-Typen mit und ohne Vorzeichen umwandeln. Etwa so (ungetestet und ich kenne die VB-Casting-Syntax nicht):

Dim hashCode = -252780983
hashCode = (Int32)((Int32)((Unt64)hashCode * -1521134295) + (UInt64)i.GetHashCode())

Woher wissen Sie, dass Folgendes nicht überläuft?

c# (Int32)((Unt64)hashCode * -1521134295)

Oder die endgültige (int32) Besetzung für diese Angelegenheit?

Ich wusste nicht, dass es überlaufgeprüfte Conv-Operationen verwenden würde. Ich denke, Sie könnten es vor dem Casting auf 32 Bit maskieren:

(Int32)(((Unt64)hashCode * -1521134295) & 0xFFFFFFFF)

vermutlich 31 Bit, da ein Wert von uint32.Max auch bei der Konvertierung in Int32 überlaufen würde :)

Das ist auf jeden Fall möglich. Hässlich... aber möglich :) Es gibt viele Besetzungen in diesem Code.

Okay. Ich denke, ich habe eine praktikable Lösung. Der Kern des Algorithmus, den wir heute generieren, ist:

c# hashCode = hashCode * -1521134295 + j.GetHashCode();

Nehmen wir an, wir machen 64-Bit-Mathematik, aber "hashCode" wurde auf 32 Bit begrenzt. Dann wird <largest_32_bit> * -1521134295 + <largest_32_bit> 64 Bit nicht überlaufen. Wir können also immer in 64 Bit rechnen und dann auf 32 (oder 32 Bit) reduzieren, um sicherzustellen, dass die nächste Runde nicht überläuft.

Vielen Dank!

@MaStr11 @morganbr @sharwell und alle hier. Ich habe meinen Code aktualisiert, um Folgendes für VB zu generieren:

        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)

Kann mich jemand vernünftig überprüfen, um sicherzustellen, dass dies Sinn macht und auch bei aktiviertem Modus nicht überlaufen sollte?

@ CyrusNajmabadi , das wird nicht überlaufen (weil Int64.Max = Int32.Max * Int32.Max und Ihre Konstanten viel kleiner sind), aber Sie maskieren das hohe Bit auf Null, es ist also nur ein 31-Bit-Hash. Wird es als Überlauf betrachtet, das High-Bit eingeschaltet zu lassen?

@CyrusNajmabadi hashCode ist ein Long , das zwischen 0 und Integer.MaxValue . Warum bekomme ich das?

image

Aber nein, es kann nicht wirklich überlaufen.

Übrigens: Ich würde Roslyn lieber ein NuGet-Paket hinzufügen lassen, als einen suboptimalen Hash hinzuzufügen.

aber Sie maskieren das High-Bit auf Null, also ist es nur ein 31-Bit-Hash. Wird es als Überlauf betrachtet, das High-Bit eingeschaltet zu lassen?

Das ist ein guter Punkt. Ich glaube, ich dachte an einen anderen Algorithmus, der uints verwendet. Um also sicher von der langen in eine uint zu konvertieren, musste ich das Vorzeichenbit nicht einschließen. Da dies jedoch alles Mathematik mit Vorzeichen ist, denke ich, dass es in Ordnung wäre, nur gegen 0xffffffff zu maskieren, um sicherzustellen, dass wir nach dem Hinzufügen jedes Eintrags nur die unteren 32 Bit beibehalten.

Ich würde Roslyn lieber ein NuGet-Paket hinzufügen lassen, als einen suboptimalen Hash hinzuzufügen.

Benutzer können dies bereits tun, wenn sie möchten. Hier geht es darum, was zu tun ist, wenn Benutzer diese Abhängigkeiten nicht hinzufügen können oder können. Dabei geht es auch darum, den Benutzern einen einigermaßen „gut genug“ Hash bereitzustellen. dh etwas Besseres als der übliche "x + y + z"-Ansatz, den die Leute oft verfolgen. Es ist nicht als „optimal“ gedacht, da es keine gute Definition dafür gibt, was „optimal“ in Bezug auf Hashing für alle Benutzer ist. Beachten Sie, dass wir hier den Ansatz verfolgen, der bereits vom Compiler für anonyme Typen ausgegeben wird. Es zeigt ein einigermaßen gutes Verhalten, während es dem Code des Benutzers nicht viel Komplexität hinzufügt. Im Laufe der Zeit, da immer mehr Benutzer in der Lage sind, sich vorwärts zu bewegen, können solche langsam verschwinden und für die meisten Menschen durch HashCode.Combine ersetzt werden.

Also habe ich ein bisschen daran gearbeitet und mir folgendes einfallen lassen, das meiner Meinung nach alle Bedenken adressiert:

        Dim hashCode As Long = 2118541809
        hashCode = (hashCode * -1521134295 + a.GetHashCode()).GetHashCode()
        hashCode = (hashCode * -1521134295 + b.GetHashCode()).GetHashCode()
        Return CType(hashCode, Integer)

Der interessanteste Teil ist der Aufruf von .GetHashCode() für den int64-Wert, der von (hashCode * -1521134295 + a.GetHashCode()) . Der Aufruf von .GetHashCode für diesen 64-Bit-Wert hat zwei gute Eigenschaften für unsere Bedürfnisse. Erstens stellt es sicher, dass hashCode immer nur einen zulässigen int32-Wert darin speichert (was die endgültige Rückgabe immer sicher macht). Zweitens stellt es sicher, dass wir keine wertvollen Informationen in den oberen 32 Bit des int64-Temp-Werts verlieren, mit dem wir arbeiten.

@CyrusNajmabadi Eigentlich wollte ich das Paket installieren, wonach ich gefragt habe. Erspart mir das machen zu müssen.

Wenn Sie HashCode eingeben und System.HashCode in einem MS-Nuget-Paket bereitgestellt wird, bietet Roslyn es an.

Ich möchte, dass es die nicht vorhandene GetHashCode-Überladung generiert und das Paket im selben Vorgang installiert.

Ich denke, das ist für die meisten Benutzer keine geeignete Wahl. Das Hinzufügen von Abhängigkeiten ist ein sehr schwerer Vorgang, zu dem Benutzer nicht gezwungen werden sollten. Benutzer können den richtigen Zeitpunkt für diese Entscheidungen festlegen, und die IDE wird dies respektieren. Das war der Ansatz, den wir bisher bei all unseren Funktionen verfolgt haben, und es ist ein gesunder Ansatz, den die Leute zu mögen scheinen.

Hinweis: In welchem ​​Nuget-Paket ist diese API überhaupt enthalten, damit wir einen Verweis hinzufügen können?

Die Implementierung befindet sich in System.Private.CoreLib.dll, würde also als Teil des Laufzeitpakets enthalten sein. Der Vertrag ist System.Runtime.dll.

Okay. Wenn dies der Fall ist, hört es sich so an, als würde ein Benutzer dies erhalten, wenn/wenn er zu einem neueren Target-Framework wechselt. So etwas ist überhaupt kein Schritt, den ich mit dem "Generieren von Equals + Hashcode" für das Projekt eines Benutzers ausführen lassen würde.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen