Transitividad de la especialización automática en GHC

392

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 Numinstancia 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.

crockeea
fuente
33
No sé la respuesta, pero parece que podría estar relacionado con: ghc.haskell.org/trac/ghc/ticket/5928 Probablemente valga la pena abrir un nuevo ticket o agregar su información allí si cree que probablemente esté relacionado con 5928
jberryman
66
@jberryman Parece que hay dos diferencias entre ese boleto y mi pregunta: 1) En el boleto, el equivalente de noplus 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
crockeea
55
Ah bien. ¿Qué sucede cuando define 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?
jberryman
3
@jberryman Divertido deberías preguntar. He recorrido ese camino con esta pregunta que condujo a este informe de seguimiento . Originalmente tuve la llamada para plusaplicar completamente debido específicamente a esos enlaces, pero de hecho obtuve menos especialización: la llamada a plustampoco 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.
crockeea
11
De ghc.haskell.org/trac/ghc/wiki/ReportABug : "En caso de duda, solo informe de su error". No debería sentirse mal, especialmente porque una cantidad suficiente de haskellers realmente experimentados aquí no saben cómo responder a su pregunta. Los casos de prueba como este probablemente sean realmente valiosos para los desarrolladores de GHC. ¡De todos modos, buena suerte! Se actualizó la pregunta si presenta un boleto
jberryman

Respuestas:

4

Respuestas cortas:

Los puntos clave de la pregunta, según tengo entendido, son los siguientes:

  • "¿es transitiva la especialización automática?"
  • ¿Debo esperar que (+) se especialice transitivamente con un pragma explícito?
  • (aparentemente previsto) ¿Es esto un error de GHC? ¿Es inconsistente con la documentación?

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 SPECIALISEpragma.

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 fes una función cuyo tipo incluye una variable de tipo arestringida por una clase de tipo C a. GHC por defecto se especializa fcon respecto a una aplicación de tipo (sustituyendo apara t) si fse llama con ese tipo de aplicación en el código fuente de (a) cualquier función en el mismo módulo, o (b) si festá marcado INLINABLE, entonces cualquier otro módulo que importaciones f de B. Por lo tanto, la especialización automática no es transitiva, solo toca las INLINABLEfunciones importadas y solicitadas en el código fuente de A.

En su ejemplo, si reescribe la instancia de la Numsiguiente manera:

instance (Num r, Unbox r) => Num (Qux r) where
    (+) = quxAdd

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddno es específicamente importado por Main. Mainimporta el diccionario de instancias de Num (Qux Int), y este diccionario contiene quxAdden el registro de (+). Sin embargo, aunque el diccionario se importa, los contenidos utilizados en el diccionario no lo son.
  • plusno llama quxAdd, utiliza la función almacenada para el (+)registro en el diccionario de instancias de Num t. Este diccionario se establece en el sitio de la llamada (en Main) por el compilador.
Diego E. Alonso-Blas
fuente