De los documentos para GHC 7.6:
[Y] a menudo ni siquiera necesitas el pragma SPECIALIZE 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 en los que se llama en M.
y
Además, dado un pragma SPECIALIZE para una función f, GHC creará automáticamente especializaciones para cualquier función sobrecargada de tipo-clase llamada por f, si están en el mismo módulo que el pragma SPECIALIZE, o si son INLINABLES; y así sucesivamente.
Por lo tanto, GHC debería especializar automáticamente algunas / más / todas (?) Funciones marcadas INLINABLE
sin un pragma, y si uso un pragma explícito, la especialización es transitiva. Mi pregunta es: ¿la transespecialización automática es transitiva?
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 se especializa en la llamada a plus
, pero no se especializa (+)
en la Qux
Num
instancia que mata el rendimiento.
Sin embargo, un pragma explícito
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
da como resultado la especialización transitiva como indican los documentos, por lo que (+)
está especializado y el código es 30 veces más rápido (ambos compilados con -O2
). ¿Es este comportamiento esperado? ¿Debería esperar solo (+)
estar especializado 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.
plus
estaba marcado como INLINABLE y 2) simonpj indicó que había algo en línea con el código del boleto, pero el núcleo de mi ejemplo muestra que ninguna de las funciones estaba en línea (en particular, no pude deshacerme del segundo constructor, de lo contrario GHC en línea).Foo
plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
, para que el LHS se aplique completamente en el sitio de la llamada? ¿Se alinea y luego comienza la especialización?plus
aplicar completamente debido específicamente a esos enlaces, pero de hecho obtuve menos especialización: la llamada aplus
tampoco estaba especializada. No tengo ninguna explicación para eso, pero tenía la intención de dejarlo para otra pregunta, o espero que se resuelva en una respuesta a esta.Respuestas:
Respuestas cortas:
Los puntos clave de la pregunta, según tengo entendido, son los siguientes:
AFAIK, las respuestas son No, principalmente sí, pero hay otros medios, y No.
La especialización de código en línea y tipo de aplicación es una compensación entre la velocidad (tiempo de ejecución) y el tamaño del código. El nivel predeterminado se acelera sin hinchar el código. La elección de un nivel más exhaustivo se deja a discreción del programador a través de
SPECIALISE
pragma.Explicación:
Supongamos que
f
es una función cuyo tipo incluye una variable de tipoa
restringida por una clase de tipoC a
. GHC por defecto se especializaf
con respecto a una aplicación de tipo (sustituyendoa
parat
) sif
se llama con ese tipo de aplicación en el código fuente de (a) cualquier función en el mismo módulo, o (b) sif
está marcadoINLINABLE
, entonces cualquier otro módulo que importacionesf
deB
. Por lo tanto, la especialización automática no es transitiva, solo toca lasINLINABLE
funciones importadas y solicitadas en el código fuente deA
.En su ejemplo, si reescribe la instancia de la
Num
siguiente manera:quxAdd
no es específicamente importado porMain
.Main
importa el diccionario de instancias deNum (Qux Int)
, y este diccionario contienequxAdd
en el registro de(+)
. Sin embargo, aunque el diccionario se importa, los contenidos utilizados en el diccionario no lo son.plus
no llamaquxAdd
, utiliza la función almacenada para el(+)
registro en el diccionario de instancias deNum t
. Este diccionario se establece en el sitio de la llamada (enMain
) por el compilador.fuente