2012-12-29 9 views
18

Ich arbeite an einigen schweren CPU gebunden Problem. Ich sehe eine große Leistungsverbesserung, wenn ich das Schlüsselwort inline verwende. einen Wörterbuch aus der Standard-.net-Bibliothek Gang in einem benutzerdefinierten Schlüssel Comparer erstellen siehe Code und Timing Ergebnisse unterWarum verursacht F # Inline 11x Leistungsverbesserung

https://gist.github.com/4409734

ohne Schlüsselwort inline auf Eq_cmp

> perf_run 10000000 ;; 
Real: 00:00:11.039, CPU: 00:00:11.029, GC gen0: 771, gen1: 3, gen2: 1 
val it : unit =() 

Inline-Schlüsselwort auf Eq_cmp

perf_run 10000000 ;; 
Real: 00:00:01.319, CPU: 00:00:01.388, GC gen0: 1, gen1: 1, gen2: 1 
val it : unit =() 
> 

Ich bemerkte auch den großen Unterschied in der Menge von Gen 0 GC mit dem INI Code und nicht inline Code.

Könnte jemand erklären, warum es einen so großen Unterschied gibt?

+0

Sie sind überrascht, wenn eine Optimierung die Leistung verbessert? Dies ist ein vollständig erwartetes Verhalten, obwohl zugegebenermaßen die Größe des Effekts vergleichsweise groß ist. –

+1

Generische Gleichheitstests in F # sind langsam. Ich denke, das ist im Wesentlichen das gleiche Problem wie das hier besprochene: http://stackoverflow.com/questions/6104221/why-is-is-f-code-so-slow/6104300#6104300 –

Antwort

17

kann ich das Verhalten auf meinem Rechner mit 3-fach Leistungssteigerung reproduzieren nach inline Schlüsselwort hinzufügen.

Das Dekompilieren von zwei Versionen nebeneinander unter ILSpy ergibt fast identischen C# -Code. Der bemerkenswerteste Unterschied ist in zwei Gleichheitstests:

// Version without inline 
bool IEqualityComparer<Program.Pair<a>>.System-Collections-Generic-IEqualityComparer(Program.Pair<a> x, Program.Pair<a> y) 
{ 
    a [email protected] = [email protected]; 
    a [email protected] = [email protected]; 
    if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<a>([email protected], [email protected])) 
    { 
     a [email protected] = [email protected]; 
     a [email protected] = [email protected]; 
     return LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<a>([email protected], [email protected]); 
    } 
    return false; 
} 

// Version with inline 
bool IEqualityComparer<Program.Pair<int>>.System-Collections-Generic-IEqualityComparer(Program.Pair<int> x, Program.Pair<int> y) 
{ 
    int [email protected] = [email protected]; 
    int [email protected] = [email protected]; 
    if ([email protected] == [email protected]) 
    { 
     int [email protected] = [email protected]; 
     int [email protected] = [email protected]; 
     return [email protected] == [email protected]; 
    } 
    return false; 
} 

Die allgemeine Gleichheit ist viel weniger effizient als die spezielle Version.

Ich bemerkte auch den großen Unterschied in der Menge von Gen 0 GC mit dem Inline-Code und nicht inline-Code.

Könnte jemand erklären, warum es einen so großen Unterschied gibt?

Wirft man einen Blick auf GenericEqualityIntrinsic Funktion in F# source code:

let rec GenericEqualityIntrinsic (x : 'T) (y : 'T) : bool = 
    fsEqualityComparer.Equals((box x), (box y)) 

Es tut Boxen auf Argumente, die die erhebliche Menge an Müll in Ihrem ersten Beispiel erklärt. Wenn GC zu oft ins Spiel kommt, wird die Berechnung drastisch verlangsamt. Das zweite Beispiel (unter Verwendung von inline) erzeugt fast keinen Müll, wenn Pair struct ist.

Das heißt, es ist das erwartete Verhalten von inline Schlüsselwort, wenn eine spezialisierte Version auf der Aufrufseite verwendet wird. Mein Vorschlag ist immer zu versuchen, Ihren Code auf denselben Benchmarks zu optimieren und zu messen.

Sie könnten an einem sehr ähnlichen Thread Why is this F# code so slow? interessiert sein.

+0

Vielen Dank, dass die Dinge geklärt –

15

Typ Spezialisierung

Ohne inline, verwenden Sie generischen Vergleich, die sehr ineffizient ist. Mit inline wird die Generizität entfernt und int Vergleich wird direkt verwendet.

+0

Warum habe ich zwei unkommentierte Downvotes ?! –

+0

Interessiert zu wissen, wie ocaml dies handhabt, da es keine Entsprechung zu Inline-AFAIK gibt –

+3

@ User125 OCaml behandelt dies sehr schlecht. Jeder Wert, für den mindestens 1 zu speicherndes Wort erforderlich ist, wird standardmäßig eingerahmt (obwohl es einen speziellen Fall gibt, Arrays von Fließkommazahlen zu entfernen, aber Laufzeittests für alle Arrays erforderlich sind). Jede generische Funktion führt Polymorphie über Laufzeit-Dispatch aus, was langsam ist. Inlining ist bestenfalls naiv (kleine Blattfunktionen werden inline), aber auch durch Funktorgrenzen behindert. So ist OCamls Hashtbl.t ein Array von (Heap-allocated) Listen von (getaggten) Schlüsseln und Werten. Custom Vergleich und Hashing bedeutet Funktoren, was gebrochenes Inlining bedeutet. –

Verwandte Themen