De la documentation pour GHC 7.6:
[V] ous n'avez souvent même pas besoin du pragma SPECIALIZE en premier lieu. Lors de la compilation d'un module M, l'optimiseur de GHC (avec -O) considère automatiquement chaque fonction surchargée de niveau supérieur déclarée dans M, et la spécialise pour les différents types auxquels il est appelé dans M. L'optimiseur considère également chaque fonction surchargée INLINABLE importée, et le spécialise pour les différents types auxquels il est appelé en M.
et
De plus, étant donné un pragma SPECIALIZE pour une fonction f, GHC créera automatiquement des spécialisations pour toutes les fonctions surchargées de classe de type appelées par f, si elles sont dans le même module que le pragma SPECIALIZE, ou si elles sont INLINABLE; et ainsi de suite, de manière transitoire.
Ainsi, GHC devrait spécialiser automatiquement certaines fonctions / la plupart / toutes (?) Marquées INLINABLE
sans pragma, et si j'utilise un pragma explicite, la spécialisation est transitive. Ma question est: l' auto- spécialisation est- elle transitive?
Plus précisément, voici un petit exemple:
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 spécialise l'appel à plus
, mais ne se spécialise pas(+)
dans l' Qux
Num
instance qui tue les performances.
Cependant, un pragma explicite
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
se traduit par une spécialisation transitive comme l'indiquent les documents, il (+)
est donc spécialisé et le code est 30 fois plus rapide (tous deux compilés avec -O2
). Est-ce un comportement attendu? Dois-je seulement m'attendre (+)
à être spécialisé de manière transitoire avec un pragma explicite?
MISE À JOUR
Les documents pour 7.8.2 n'ont pas changé, et le comportement est le même, donc cette question est toujours d'actualité.
plus
n'était pas marqué comme INLINABLE et 2) simonpj a indiqué qu'il y avait un certain alignement en cours avec le code du ticket, mais le noyau de mon exemple montre qu'aucune des fonctions n'était en ligne (en particulier, je ne pouvais pas me débarrasser du deuxièmeFoo
constructeur, sinon les choses en ligne de GHC).plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
, afin que le LHS soit pleinement appliqué sur le site d'appel? Est-il aligné puis la spécialisation se déclenche-t-elle?plus
appliquer pleinement en raison de ces liens, mais en fait j'ai eu moins de spécialisation: l'appel àplus
n'était pas spécialisé non plus. Je n'ai aucune explication à cela, mais j'avais l'intention de le laisser pour une autre question, ou j'espère que cela sera résolu dans une réponse à celle-ci.Réponses:
Réponses courtes:
Si je comprends bien, les points clés de la question sont les suivants:
AFAIK, les réponses sont non, surtout oui mais il y a d'autres moyens, et non.
L'intégration de code et la spécialisation d'application de type est un compromis entre la vitesse (temps d'exécution) et la taille du code. Le niveau par défaut obtient une certaine vitesse sans gonfler le code. Le choix d'un niveau plus exhaustif est laissé à la discrétion du programmeur via
SPECIALISE
pragma.Explication:
Supposons que
f
soit une fonction dont le type inclut une variable de typea
contrainte par une classe de typeC a
. GHC se spécialise par défaut en cef
qui concerne une application de type (en remplacementa
det
) si ellef
est appelée avec cette application de type dans le code source de (a) n'importe quelle fonction du même module, ou (b) si ellef
est marquéeINLINABLE
, alors tout autre module qui importef
deB
. Ainsi, l'auto-spécialisation n'est pas transitive, elle ne touche que lesINLINABLE
fonctions importées et demandées dans le code source deA
.Dans votre exemple, si vous réécrivez l'instance
Num
comme suit:quxAdd
n'est pas spécifiquement importé parMain
.Main
importe le dictionnaire d'instances deNum (Qux Int)
, et ce dictionnaire contientquxAdd
dans l'enregistrement de(+)
. Cependant, bien que le dictionnaire soit importé, le contenu utilisé dans le dictionnaire ne l'est pas.plus
n'appelle pasquxAdd
, il utilise la fonction stockée pour l'(+)
enregistrement dans le dictionnaire d'instances deNum t
. Ce dictionnaire est défini sur le site d'appel (enMain
) par le compilateur.la source