Aus den Dokumenten für GHC 7.6:
[Y] Oft brauchen Sie gar nicht erst das Pragma SPEZIALISIEREN. Beim Kompilieren eines Moduls M berücksichtigt der Optimierer von GHC (mit -O) automatisch jede in M deklarierte überladene Funktion der obersten Ebene und ist auf die verschiedenen Typen spezialisiert, bei denen es in M aufgerufen wird. Der Optimierer berücksichtigt auch jede importierte INLINABLE überladene Funktion. und spezialisiert es auf die verschiedenen Typen, bei denen es in M genannt wird.
und
Darüber hinaus erstellt GHC bei einem SPECIALIZE-Pragma für eine Funktion f automatisch Spezialisierungen für alle von f aufgerufenen Funktionen, die von Typklassen überladen sind, wenn sie sich im selben Modul wie das SPECIALIZE-Pragma befinden oder INLINABLE sind. und so weiter, transitiv.
Daher sollte GHC einige / die meisten / alle (?) Funktionen, die INLINABLE
ohne Pragma markiert sind, automatisch spezialisieren. Wenn ich ein explizites Pragma verwende, ist die Spezialisierung transitiv. Meine Frage ist: Ist die Auto- Spezialisierung transitiv?
Hier ist ein kleines Beispiel:
Main.hs:
import Data.Vector.Unboxed as U
import Foo
main =
let y = Bar $ Qux $ U.replicate 11221184 0 :: Foo (Qux Int)
(Bar (Qux ans)) = iterate (plus y) y !! 100
in putStr $ show $ foldl1' (*) ans
Foo.hs:
module Foo (Qux(..), Foo(..), plus) where
import Data.Vector.Unboxed as U
newtype Qux r = Qux (Vector r)
-- GHC inlines `plus` if I remove the bangs or the Baz constructor
data Foo t = Bar !t
| Baz !t
instance (Num r, Unbox r) => Num (Qux r) where
{-# INLINABLE (+) #-}
(Qux x) + (Qux y) = Qux $ U.zipWith (+) x y
{-# INLINABLE plus #-}
plus :: (Num t) => (Foo t) -> (Foo t) -> (Foo t)
plus (Bar v1) (Bar v2) = Bar $ v1 + v2
GHC ist auf den Aufruf von spezialisiert plus
, jedoch nicht auf (+)
die Qux
Num
Instanz, die die Leistung beeinträchtigt.
Ein explizites Pragma
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
führt zu einer transitiven Spezialisierung, wie in den Dokumenten angegeben, (+)
ist also spezialisiert und der Code ist 30x schneller (beide kompiliert mit -O2
). Ist das erwartetes Verhalten? Sollte ich nur erwarten (+)
, mich transitiv mit einem expliziten Pragma zu spezialisieren?
AKTUALISIEREN
Die Dokumente für 7.8.2 haben sich nicht geändert, und das Verhalten ist dasselbe, sodass diese Frage weiterhin relevant ist.
plus
wurde nicht als INLINABLE markiert und 2) simonpj angegeben , dass es einige inlining los mit dem Ticket - Code, aber der Kern aus Mein Beispiel zeigt, dass keine der Funktionen inline war (insbesondere konnte ich den zweitenFoo
Konstruktor nicht loswerden , sonst GHC inlined).plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
, dass die LHS am Anrufort vollständig angewendet wird? Wird es inline und setzt dann die Spezialisierung ein?plus
, mich aufgrund dieser Links vollständig zu bewerben, aber tatsächlich wurde ich weniger spezialisiert: Der Anruf anplus
war auch nicht spezialisiert. Ich habe keine Erklärung dafür, wollte es aber für eine andere Frage belassen oder hoffe, dass es in einer Antwort auf diese Frage gelöst wird.Antworten:
Kurze Antworten:
Die wichtigsten Punkte der Frage sind nach meinem Verständnis die folgenden:
AFAIK, die Antworten sind Nein, meistens ja, aber es gibt andere Mittel und Nein.
Code-Inlining und Typanwendungsspezialisierung sind ein Kompromiss zwischen Geschwindigkeit (Ausführungszeit) und Codegröße. Die Standardstufe wird etwas beschleunigt, ohne den Code aufzublähen. Die Wahl eines umfassenderen Levels liegt im Ermessen des Programmierers über
SPECIALISE
Pragma.Erläuterung:
Angenommen, es
f
handelt sich um eine Funktion, deren Typ eine Typvariable enthält,a
die durch eine Typklasse eingeschränkt wirdC a
. GHC standardmäßig spezialisiertf
in Bezug auf eine Art Anwendung (Substitutiona
fürt
) , wennf
mit dieser Art Anwendung in dem Quellcode (a) jede Funktion in dem gleichen Modul oder (b) aufgerufen wird , wennf
gekennzeichnet wirdINLINABLE
, dann andere Modul , das Einfuhrenf
vonB
. Die automatische Spezialisierung ist also nicht transitiv, sondern berührt nurINLINABLE
Funktionen, die im Quellcode von importiert und aufgerufen werdenA
.Wenn Sie in Ihrem Beispiel die Instanz
Num
wie folgt umschreiben :quxAdd
wird nicht speziell von importiertMain
.Main
importiert das Instanzwörterbuch vonNum (Qux Int)
, und dieses Wörterbuch enthältquxAdd
im Datensatz für(+)
. Obwohl das Wörterbuch importiert wird, werden die im Wörterbuch verwendeten Inhalte nicht importiert.plus
ruft nicht aufquxAdd
, es verwendet die Funktion, die für den(+)
Datensatz im Instanzwörterbuch von gespeichert istNum t
. Dieses Wörterbuch wirdMain
vom Compiler an der Aufrufstelle (in ) festgelegt.quelle