Je rencontre des problèmes pour que GHC spécialise une fonction avec une contrainte de classe. J'ai un exemple minimal de mon problème ici: Foo.hs et Main.hs . Les deux fichiers se compilent (GHC 7.6.2, ghc -O3 Main
) et courir.
REMARQUE: Foo.hs
est vraiment dépouillé. Si vous voulez voir pourquoi la contrainte est nécessaire, vous pouvez voir un peu plus de code ici . Si je mets le code dans un seul fichier ou que j'apporte de nombreuses autres modifications mineures, GHC insère simplement l'appel à plusFastCyc
. Cela ne se produira pas dans le code réel car plusFastCyc
est trop grand pour que GHC soit en ligne, même lorsqu'il est marqué INLINE
. Le point est de se spécialiser l'appel à plusFastCyc
, pas en ligne. plusFastCyc
est appelé à de nombreux endroits dans le vrai code, donc la duplication d'une fonction aussi grande ne serait pas souhaitable même si je pouvais forcer GHC à le faire.
Le code d'intérêt est le plusFastCyc
dans Foo.hs
, reproduit ici:
{-# INLINEABLE plusFastCyc #-}
{-# SPECIALIZE plusFastCyc ::
forall m . (Factored m Int) =>
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) #-}
-- Although the next specialization makes `fcTest` fast,
-- it isn't useful to me in my real program because the phantom type M is reified
-- {-# SPECIALIZE plusFastCyc ::
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int #-}
plusFastCyc :: (Num (t r)) => (FastCyc t r) -> (FastCyc t r) -> (FastCyc t r)
plusFastCyc (PowBasis v1) (PowBasis v2) = PowBasis $ v1 + v2
Le Main.hs
le fichier a deux pilotes: vtTest
, qui s'exécute en ~ 3 secondes, et fcTest
, qui s'exécute en ~ 83 secondes lorsqu'il est compilé avec -O3 en utilisant le forall
'd spécialisation.
Le le noyau montre que pour le test vtTest
, le code d'addition est spécialisé dans les vecteurs Unboxed
sur Int
s, etc., tandis que le code vectoriel générique est utilisé pour fcTest
. À la ligne 10, vous pouvez voir que GHC écrit une version spécialisée de plusFastCyc
, par rapport à la version générique de la ligne 167. La règle de spécialisation est à la ligne 225. Je pense que cette règle devrait se déclencher à la ligne 270 . (main6
appels iterate main8 y
, donc main8
est l'endroit où plusFastCyc
doit être spécialisé.)
Mon objectif est de rendre fcTest
aussi rapide que vtTest
en spécialisant plusFastCyc
. J'ai trouvé deux façons de procéder:
inline
de GHC.Exts
dans fcTest
.Factored m Int
contrainte sur plusFastCyc
.L'option 1 n'est pas satisfaisante car dans la base de code réelle plusFastCyc
est une opération fréquemment utilisée et une fonction très grande, elle ne doit donc pas être insérée à chaque utilisation. Au lieu de cela, GHC devrait appeler une version spécialisée de plusFastCyc
. L'option 2 n'est pas vraiment une option car j'ai besoin de la contrainte dans le vrai code.
J'ai essayé une variété d'options en utilisant (et non en utilisant) INLINE
, INLINABLE
et SPECIALIZE
, mais rien ne semble fonctionner. ( [~ # ~] modifier [~ # ~] : J'ai peut-être supprimé trop de plusFastCyc
pour rendre mon exemple petit , donc INLINE
peut entraîner l'inclusion de la fonction. Cela ne se produit pas dans mon code réel car plusFastCyc
est si grand.) Dans cet exemple particulier, je ne reçois aucun - match_co: needs more cases
ou RULE: LHS too complicated to desugar
(et ici ) avertissements, même si je recevais de nombreux match_co
avertissements avant de minimiser l'exemple. Vraisemblablement, le "problème" est le Factored m Int
contrainte dans la règle; si j'apporte des modifications à cette contrainte, fcTest
s'exécute aussi vite que vtTest
.
Suis-je en train de faire quelque chose que GHC n'aime pas? Pourquoi GHC ne spécialise-t-il pas le plusFastCyc
, et comment puis-je le faire?
[~ # ~] mise à jour [~ # ~]
Le problème persiste dans GHC 7.8.2, donc cette question est toujours d'actualité.
GHC donne également une option à SPECIALIZE
une déclaration d'instance de classe de type. J'ai essayé cela avec le code (étendu) de Foo.hs
, en mettant ce qui suit:
instance (Num r, V.Vector v r, Factored m r) => Num (VT v m r) where
{-# SPECIALIZE instance ( Factored m Int => Num (VT U.Vector m Int)) #-}
VT x + VT y = VT $ V.zipWith (+) x y
Ce changement, cependant, n'a pas atteint l'accélération souhaitée. Ce qui a permis cette amélioration des performances était manuellement l'ajout d'une instance spécialisée pour le type VT U.Vector m Int
avec les mêmes définitions de fonction, comme suit:
instance (Factored m Int) => Num (VT U.Vector m Int) where
VT x + VT y = VT $ V.zipWith (+) x y
Cela nécessite l'ajout de OverlappingInstances
et FlexibleInstances
dans LANGUAGE
.
Fait intéressant, dans l'exemple de programme, l'accélération obtenue avec l'instance qui se chevauche reste même si vous supprimez chaque pragma SPECIALIZE
et INLINABLE
.