web-dev-qa-db-fra.com

Spécialisation avec contraintes

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 Ints, 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:

  1. Appel explicite inline de GHC.Exts dans fcTest.
  2. Retirer le 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é.

154
crockeea

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.

4