Dos documentos para o GHC 7.6:
Em geral, você nem precisa do pragma SPECIALIZE. Ao compilar um módulo M, o otimizador do GHC (com -O) considera automaticamente cada função de sobrecarga de nível superior declarada em M e a especializa para os diferentes tipos em que é chamado em M. O otimizador também considera cada função de sobrecarga INLINABLE importada, e especializa-o para os diferentes tipos em que é chamado em M.
e
Além disso, dado um pragma SPECIALIZE para uma função f, o GHC criará automaticamente especializações para quaisquer funções sobrecarregadas de classe de tipo chamadas por f, se estiverem no mesmo módulo que o pragma SPECIALIZE ou se forem INLINABLE; e assim por diante, transitivamente.
Portanto, o GHC deve especializar automaticamente algumas / a maioria / todas as funções (?) Marcadas INLINABLE
sem um pragma e, se eu usar um pragma explícito, a especialização é transitiva. Minha pergunta é: a auto-especialização é transitiva?
Especificamente, aqui está um pequeno exemplo:
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
O GHC especializa a chamada plus
, mas não se especializa (+)
na Qux
Num
instância que mata o desempenho.
No entanto, um pragma explícito
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
resulta em especialização transitiva, conforme indicado pelos documentos, por isso (+)
é especializado e o código é 30x mais rápido (ambos compilados -O2
). Esse comportamento é esperado? Devo apenas esperar (+)
ser especializado transitivamente com um pragma explícito?
ATUALIZAR
Os documentos para 7.8.2 não foram alterados e o comportamento é o mesmo, portanto, essa pergunta ainda é relevante.
plus
foi marcado como INLINABLE e 2) simonpj indicou que havia algo de errado com o código do ticket, mas o núcleo de meu exemplo mostra que nenhuma das funções estava embutida (em particular, eu não conseguia me livrar do segundo construtor, caso contrário, coisas embutidas no GHC).Foo
plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
, para que o LHS seja totalmente aplicado no local da chamada? Ele é embutido e a especialização é ativada?plus
totalmente aplicada devido a esses links, mas, na verdade, recebi menos especialização: a chamada paraplus
também não era especializada. Não tenho explicação para isso, mas pretendia deixar para outra pergunta, ou espero que isso seja resolvido em resposta a esta.Respostas:
Respostas curtas:
Os pontos principais da pergunta, como eu os entendo, são os seguintes:
AFAIK, as respostas são Não, principalmente sim, mas existem outros meios e Não.
A inserção de código e a especialização de aplicativo de tipo são uma troca entre velocidade (tempo de execução) e tamanho do código. O nível padrão obtém alguma aceleração sem inchar o código. A escolha de um nível mais exaustivo fica a critério do programador via
SPECIALISE
pragma.Explicação:
Suponha que
f
é uma função cujo tipo inclui uma variável de tipoa
restrita por uma classe de tipoC a
. GHC por padrão especializadaf
no que diz respeito a um tipo de aplicação (substituindoa
at
) sef
é chamado com que tipo de aplicação no código fonte de (a) qualquer função no mesmo módulo, ou (b) sef
for marcadoINLINABLE
, então qualquer outro módulo que importaçõesf
deB
. Assim, a autoespecialização não é transitiva, apenas tocaINLINABLE
funções importadas e solicitadas no código fonte deA
.No seu exemplo, se você reescrever a instância da
Num
seguinte maneira:quxAdd
não é importado especificamente porMain
.Main
importa o dicionário de instância deNum (Qux Int)
e esse dicionário contémquxAdd
no registro para(+)
. No entanto, embora o dicionário seja importado, o conteúdo usado no dicionário não é.plus
não chamaquxAdd
, usa a função armazenada para o(+)
registro no dicionário de instância deNum t
. Este dicionário é definido no site da chamada (inMain
) pelo compilador.fonte