ich diese Grundtypen in meinem Code haben:Haskell Leistung von Beispiel
newtype Foo (m :: Factored) = Foo Int64 deriving (NFData)
foo :: forall m . (Fact m) => Foo m -> Foo m
class T t where t :: (Fact m) => t m -> t m
instance T Foo where t = foo
newtype Bar t (m :: Factored) = Bar (t m) deriving (NFData)
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
(ignorieren Fact
und Factored
für den Moment). Ich benchmarkiere den Code auf verschiedenen Ebenen und vergleiche die Leistung von foo
, t
und bar
. In den Benchmarks und bar
gilt nur t
über eine newtype
. So sollte ihre Laufzeit im Wesentlichen identisch sein, aber criterion berichtet, dass foo
dauert 9,2ns, t
dauert etwa doppelt so hoch bei 17,45ns, und bar
dauert eine satte 268,1ns.
Ich habe mit dem Hinzufügen INLINABLE
und sogar einem SPECIALIZE
Pragma experimentiert, aber sie helfen nicht. Ich möchte glauben, dass GHC einige magische Syntax/Optimierung/etc hat, die einheitlich angewandt werden kann, um diese Art von Leistungsproblemen zu lösen. Zum Beispiel habe ich Fälle gesehen, in denen das Schreiben von Code im Point-Free-Stil dramatische Auswirkungen auf die Leistung hat.
Der vollständige Code kann here gefunden werden. Ich verspreche, es ist nicht einschüchternd. Die Module sind:
- Foo: definiert
Foo
,foo
undT
- Bar: definiert
Bar
undbar
- FooBench: definiert als Maßstab für
foo
- TBench: eine Benchmark für
t
definiert
- BarBench: definiert einen Benchmark für
bar
- Haupt: läuft die drei Benchmarks
- einkalkuliert: Die meisten der Module definiert
Fact
undFactored
singletons
verwenden, sind winzig; Ich habe die drei Benchmarks in separaten Dateien definiert, damit ich ihren Kern untersuchen konnte. Ich habe den Kern für die drei Module *Bench
erzeugt und so gut wie möglich ausgerichtet. Sie sind nur ~ 250 Zeilen und die ersten ~ 200 Zeilen sind identisch, bis zur Umbenennung. Das Problem ist, dass ich nicht weiß, was ich von den letzten 50 Zeilen machen soll. Das (Kern) diff für FooBench
vs TBench
ist here, ist der Unterschied für TBench
vs BarBench
here, und der Unterschied für FooBench
vs BarBench
ist here.
Nur einige der Fragen, die ich habe:
Auf einem hohen Niveau, was ist der wesentliche Unterschied zwischen den Core-Dateien? Ich suche nach etwas wie "Hier können Sie sehen, dass GHC den Anruf zu
x
nicht inlining ist." Nach was soll ich suchen?Was kann getan werden, um die drei Benchmarks in etwa 9,2ns laufen zu lassen? GHC-Optimierungen?
INLINE
/INLINABLE
Pragmas?SPECIALIZE
Pragmas, die ich verpasst habe?(Sie dürfen sich nicht aufF128::Factored
spezialisieren; in meiner realen Bibliothek kann dieser Wert zur Laufzeit verdinglicht werden.) Einschränken/Verzögern des Inlining auf eine bestimmte Phase?
Obwohl ich für eine tatsächliche Lösung, die Benchmarks alle schnell zu machen, dann ist es möglich, dass Tricks, die für dieses Beispiel nicht funktioniert zu meiner eigentlichen Bibliothek skaliert. Daher suche ich auch nach der "high-level" Erklärung, warum eine bestimmte Technik funktionieren sollte.
Dies kann hilfreich sein: http://stackoverflow.com/questions/6121146/reading-ghc-core –
Mein _guess_ ist, dass der Unterschied, den Sie beobachten, ist die Kosten für die Weitergabe der 'Fact' und' T' Wörterbücher um –
@BenjaminHodgson Ich würde wirklich gerne Spezialisierung den ganzen Weg hinunter den Stapel bekommen, so dass es keine Wörterbücher beteiligt sind, sobald Sie auf der obersten Ebene monomorphisieren. Irgendeine Idee, wie man das macht? – crockeea