Von den Dokumenten für GHC 7.6:
Sie benötigen oft nicht einmal die SPECIALIZE-Pragma. Beim Kompilieren eines Moduls M berücksichtigt der Optimierer von GHC (mit -O) automatisch jede oberste überladene Funktion, die in M deklariert ist, und spezialisiert sie für die verschiedenen Typen, mit denen sie in M aufgerufen wird. Der Optimierer berücksichtigt auch jede importierte INLINABLE überladene Funktion und spezialisiert sie für die verschiedenen Typen, mit denen sie in M aufgerufen wird.
und
Darüber hinaus erstellt GHC bei einem SPECIALIZE-Pragma für eine Funktion f automatisch Spezialisierungen für alle durch f aufgerufenen typklassenüberladenen Funktionen, wenn sie sich im selben Modul wie das SPECIALIZE-Pragma befinden oder wenn sie INLINABLE sind; und so weiter, transitiv.
Daher sollte GHC automatisch einige/die meisten/alle(?) als INLINABLE
markierte Funktionen ohne ein Pragma spezialisieren, und wenn ich ein explizites Pragma verwende, erfolgt die Spezialisierung transitiv. Meine Frage ist: ist die auto-Spezialisierung transitiv?
Genauer gesagt, 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 inliniert `plus`, wenn ich die Ausrufezeichen oder den Baz-Konstruktor entferne
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 spezialisiert den Aufruf von plus
, spezialisiert jedoch nicht (+)
in der Qux
Num
-Instanz, was 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, sodass (+)
spezialisiert wird und der Code 30 mal schneller ist (beide mit -O2
kompiliert). Ist dieses Verhalten zu erwarten? Sollte ich nur erwarten, dass (+)
transitiv mit einem expliziten Pragma spezialisiert wird?
AKTUALISIERUNG
Die Dokumente für 7.8.2 haben sich nicht geändert, und das Verhalten ist dasselbe, daher ist diese Frage weiterhin relevant.