Die Leistung variiert dramatisch, wenn eine Funktion zwischen Modulen verschoben wird

8

Wenn ich eine Funktion von ihrem Einsatzort in ein separates Modul verschiebe, habe ich bemerkt, dass die Leistung des Programms deutlich sinkt.

%Vor%

in einem anderen Modul:

%Vor%

Dies wird in 0,1 Sekunden ausgeführt, wenn fromDigits im selben Modul ist, aber 0,4 Sekunden, wenn ich es in ein anderes Modul verschiebe.

Ich gehe davon aus, dass GHC die Funktion nicht inline einbinden kann, wenn sie sich in einem anderen Modul befindet, aber ich habe das Gefühl, sollte es können, da sie im selben Paket sind.

Ich bin nicht sicher, was die Compiler-Einstellungen sind, aber es ist mit Leksah / cabal defaults gebaut. Ich bin ziemlich sicher, dass das mit -O2 als Minimum ist.

    
Peter Hall 14.03.2012, 22:23
quelle

2 Antworten

8

Für die polymorphe Typklasse fromDigits erhalten Sie eine Funktion, die aufgrund der Dictionary-Lookups für (+) , (*) und fromInteger zu groß ist, um ihre Entfaltung automatisch verfügbar zu machen. Das bedeutet, dass es nicht auf die Call-Sites spezialisiert werden kann und die Dictionary-Lookups nicht eliminiert werden können, möglicherweise inline Addition und Multiplikation (was weitere Optimierung ermöglichen könnte).

Wenn GHC in demselben Modul definiert ist, in dem es mit Optimierungen verwendet wird, erstellt GHC eine spezialisierte Version für den Typ, in dem es verwendet wird, falls bekannt. Dann können die Dictionary-Lookups eliminiert werden, und die Operationen (+) und (*) können inline ausgeführt werden (wenn der Typ, mit dem sie verwendet werden, Operationen enthält, die für Inlining geeignet sind).

Aber das hängt davon ab, welcher Typ bekannt ist. Wenn Sie also die polymorphen calc und fromDigits in einem Modul haben, aber nur in einem anderen Modul, sind Sie wieder in der Position, dass nur die generische Version verfügbar ist, aber da seine Entfaltung nicht verfügbar ist, kann es nicht auf die Anrufseite spezialisiert oder anderweitig optimiert sein.

Eine Lösung besteht darin, die Entfaltung der Funktion in der Interface-Datei offenzulegen, damit sie dort, wo die notwendigen Daten (insbesondere der Typ) zur Verfügung stehen, richtig optimiert werden kann. Sie können die Entfaltung der Funktion in der Schnittstellendatei anzeigen, indem Sie ein {-# INLINE #-} oder, ab GHC 7, ein {-# INLINABLE #-} Pragma für die Funktion hinzufügen. Das macht den fast unveränderten Quellcode verfügbar, wenn der aufrufende Code kompiliert wird, so dass die Funktion richtig mit mehr verfügbaren Informationen optimiert werden kann.

Der Nachteil ist Code-Bloat, Sie erhalten eine Kopie des optimierten Codes an jeder Call-Site (für INLINABLE ist es nicht so extrem, Sie erhalten mindestens eine Kopie pro aufrufendem Modul, das ist normalerweise nicht so schlecht) .

Eine alternative Lösung besteht darin, spezialisierte Versionen im definierenden Modul zu erzeugen, indem {-# SPECIALISE #-} pragmas (US-Rechtschreibprüfung auch akzeptiert) hinzugefügt wird, damit GHC optimierte Versionen für die wichtigen Typen erstellen kann ( Int , Integer , Word , ?). Dadurch werden auch Rewrite-Regeln erstellt, so dass die Verwendung der specialized-for-Typen neu geschrieben wird, um die spezialisierte Version zu verwenden (beim Kompilieren mit Optimierungen).

Der Nachteil ist, dass einige Optimierungen, die möglich wären, wenn der Code inline ist, nicht sind.

    
Daniel Fischer 14.03.2012, 23:58
quelle
5

Sie können GHC anweisen, eine Funktion an der Aufruf-Site mit der Funktion inline einzufügen: Ссылка . Möglicherweise möchten Sie es in Verbindung mit dem Pragma INLINABLE verwenden: Ссылка

    
Grzegorz Chrupała 14.03.2012 23:36
quelle