haskell - pagina - Transitividad de la Auto-Especialización en GHC
haskell pagina oficial (1)
De los documentos para GHC 7.6:
[A menudo ni siquiera necesitas el pragma ESPECIALIZAR en primer lugar. Al compilar un módulo M, el optimizador de GHC (con -O) considera automáticamente cada función sobrecargada de nivel superior declarada en M, y la especializa para los diferentes tipos a los que se llama en M. El optimizador también considera cada función sobrecargada INLINABLE importada, y lo especializa para los diferentes tipos a los que se llama en M.
y
Además, dado un pragma SPECIALIZE para una función f, GHC creará automáticamente las especializaciones para cualquier función sobrecargada de clase de tipo llamada por f, si están en el mismo módulo que el pragma SPECIALIZE, o si son INLINABLES; Y así sucesivamente, transitivamente.
Por lo tanto, GHC debería especializar automáticamente some/most/all(?) funciones some/most/all(?) INLINABLE
como INLINABLE
sin pragma, y si uso un pragma explícito, la especialización es transitiva. Mi pregunta es: ¿es transitivo la autoespecialización?
Específicamente, aquí hay un pequeño ejemplo:
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 especializa la llamada a plus
, pero no se especializa (+)
en la instancia de Qux
Num
que mata el rendimiento.
Sin embargo, un pragma explícito.
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
los resultados en la especialización transitiva como indican los documentos, por lo que (+)
está especializado y el código es -O2
más rápido (ambos compilados con -O2
). ¿Es este el comportamiento esperado? ¿Debo esperar que (+)
se especialice transitivamente con un pragma explícito?
ACTUALIZAR
Los documentos para 7.8.2 no han cambiado, y el comportamiento es el mismo, por lo que esta pregunta sigue siendo relevante.
Respuestas cortas:
Los puntos clave de la pregunta, tal como los entiendo, son los siguientes:
- ¿Es transitivo la autoespecialización?
- ¿Debo esperar que (+) se especialice transitivamente con un pragma explícito?
- (aparentemente destinado) ¿Es este un error de GHC? ¿Es inconsistente con la documentación?
AFAIK, las respuestas son No, la mayoría sí, pero hay otros medios, y No.
La inclusión de código y la especialización de aplicaciones de tipo es una compensación entre velocidad (tiempo de ejecución) y tamaño de código. El nivel predeterminado obtiene un poco de aceleración sin inflar el código. La elección de un nivel más exhaustivo se deja a la discreción del programador a través del programa SPECIALISE
.
Explicación:
El optimizador también considera cada función sobrecargada INLINABLE importada, y la especializa para los diferentes tipos a los que se llama en M.
Supongamos que f
es una función cuyo tipo incluye una variable de tipo a
restringida por una clase de tipo C a
. Por defecto, GHC especializa f
con respecto a una aplicación de tipo (sustituyendo a
por t
) si f
se llama con esa aplicación de tipo en el código fuente de (a) cualquier función en el mismo módulo, o (b) si f
está marcado como INLINABLE
entonces cualquier otro módulo que importe f
desde B
. Por lo tanto, la auto-especialización no es transitiva, solo toca INLINABLE
funciones INLINABLE
importadas y solicitadas en el código fuente de A
En su ejemplo, si reescribe la instancia de Num
siguiente manera:
instance (Num r, Unbox r) => Num (Qux r) where
(+) = quxAdd
quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
-
quxAdd
no es importado específicamente porMain
.Main
importa el diccionario de instancia deNum (Qux Int)
, y este diccionario contienequxAdd
en el registro para(+)
. Sin embargo, aunque el diccionario es importado, los contenidos utilizados en el diccionario no lo son. -
plus
no llama aquxAdd
, usa la función almacenada para el registro(+)
en el diccionario de instancia deNum t
. Este diccionario se establece en el sitio de la llamada (enMain
) por el compilador.