Transitivité de l’auto-spécialisation en GHC

Des documents pour GHC 7.6:

Souvent, vous n’avez même pas besoin du pragma SPECIALIZE. 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 d’appels dans M. L’optimiseur considère également chaque fonction surchargée INLINABLE imscope, 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 INLINABLES; et ainsi de suite, de manière transitoire.

Donc, GHC devrait automatiquement spécialiser certaines / la plupart / toutes les fonctions (?) INLINABLE sans un pragma, et si j’utilise un pragma explicite, la spécialisation est transitive. Ma question est la suivante: le transitive auto- spécialisation?

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 (+) xy {-# 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’instance Qux Num 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’indique le document, donc (+) est spécialisé et le code est 30 fois plus rapide (les deux sont compilés avec -O2 ). Est-ce un comportement attendu? Dois-je m’attendre à ce que (+) soit spécialisé en transit avec un pragma explicite?


METTRE À JOUR

Les documents pour 7.8.2 n’ont pas changé et le comportement est le même, donc cette question est toujours pertinente.

Réponses courtes:

Les points clés de la question, tels que je les comprends, sont les suivants:

  • “l’auto-spécialisation est-elle transitive?”
  • Dois-je m’attendre à ce que (+) soit spécialisé en transit avec un pragma explicite?
  • (apparemment destiné) Est-ce un bug de GHC? Est-ce incompatible avec la documentation?

AFAIK, les réponses sont non, principalement oui mais il y a d’autres moyens, et non.

L’incrustation de code et la spécialisation d’application de type sont un compromis entre la vitesse (temps d’exécution) et la taille du code. Le niveau par défaut est accéléré sans gonfler le code. Le choix d’un niveau plus exhaustif est laissé à la discrétion du programmeur via le pragma SPECIALISE .

Explication:

L’optimiseur considère également chaque fonction INLINABLE surchargée imscope et la spécialise pour les différents types auxquels elle est appelée dans M.

Supposons que f soit une fonction dont le type inclut une variable de type a contrainte par une classe de type C a . GHC par défaut se spécialise dans f pour une application de type (substituant a pour t ) si f est appelé avec ce type d’application dans le code source de (a) toute fonction dans le même module, ou (b) si f est marqué INLINABLE , puis tout autre module qui importe f de B Ainsi, l’auto-spécialisation n’est pas transitive, elle ne touche INLINABLE fonctions INLINABLE imscopes et appelées dans le code source de A

Dans votre exemple, si vous réécrivez l’instance de Num comme suit:

 instance (Num r, Unbox r) => Num (Qux r) where (+) = quxAdd quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) xy 
  • quxAdd n’est pas spécifiquement importé par Main . Main importe le dictionnaire d’instance de Num (Qux Int) , et ce dictionnaire contient quxAdd dans l’enregistrement pour (+) . Cependant, bien que le dictionnaire soit importé, les contenus utilisés dans le dictionnaire ne le sont pas.
  • plus ne pas appeler quxAdd , il utilise la fonction stockée pour l’enregistrement (+) dans le dictionnaire d’instance de Num t . Ce dictionnaire est défini sur le site d’appel (en Main ) par le compilateur.