Из документов для GHC 7.6:
[Y] Вам часто даже не нужна прагма SPECIALIZE. При компиляции модуля M оптимизатор GHC (с -O) автоматически рассматривает каждую перегруженную функцию верхнего уровня, объявленную в M, и специализирует ее для различных типов, в которых она вызывается в M. Оптимизатор также учитывает каждую импортированную перегруженную функцию INLINABLE, и специализирует его для разных типов, в которых он называется в М.
а также
Более того, учитывая прагму SPECIALIZE для функции f, GHC автоматически создает специализации для любых функций, перегруженных классами типов, вызываемых f, если они находятся в том же модуле, что и прагма SPECIALIZE, или если они являются INLINABLE; и так далее, переходно.
Таким образом, GHC должен автоматически специализировать некоторые / большинство / все (?) Функции, помеченные INLINABLE
без прагмы, и если я использую явную прагму, специализация транзитивна. Мой вопрос: является ли автоспециализация транзитивной?
В частности, вот небольшой пример:
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 специализируется на вызове plus
, но не специализируется (+)
на Qux
Num
экземпляре, который убивает производительность.
Тем не менее, явная прагма
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
приводит к переходной специализации, как указывают документы, поэтому (+)
является специализированным, и код работает в 30 раз быстрее (оба скомпилированы -O2
). Это ожидаемое поведение? Стоит ли ожидать (+)
специализированной транзитивности только с явной прагмой?
ОБНОВИТЬ
Документы по 7.8.2 не изменились, и поведение остается прежним, поэтому этот вопрос по-прежнему актуален.
plus
был помечен как INLINABLE, и 2) simonpj указал, что с кодом билета происходит некоторое встраивание, но ядро из мой пример показывает, что ни одна из функций не была встроенной (в частности, я не смог избавиться от второго конструктора, в противном случае встроенный материал GHC).Foo
plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2
, так что LHS полностью применяется на сайте вызова? Становится ли это встроенным, а затем начинается специализация?plus
Из-за этих ссылок у меня изначально был призыв к полному применению, но на самом деле я получил меньшую специализацию: вызов такжеplus
не был специализированным. У меня нет объяснения этому, но я собирался оставить его для другого вопроса или надеюсь, что он будет решен в ответе на этот вопрос.Ответы:
Короткие ответы:
Ключевыми моментами вопроса, насколько я понимаю, являются следующие:
AFAIK, ответы нет, в основном да, но есть и другие средства, и нет.
Внедрение кода и специализация приложения типа - это компромисс между скоростью (временем выполнения) и размером кода. Уровень по умолчанию получает некоторое ускорение без раздувания кода. Выбор более исчерпывающего уровня оставлен на усмотрение программиста через
SPECIALISE
прагму.Объяснение:
Предположим,
f
что это функция, тип которой включает переменную типа,a
ограниченную классом типаC a
. GHC по умолчанию специализируетсяf
по отношению к типу приложения (заменяющегоa
дляt
) , еслиf
вызывается с этим типом приложения в исходном коде (а) любой функции в том же самом модуле, или (б) , еслиf
отмеченINLINABLE
, то любой другой модуль , который импортируетf
отB
. Таким образом, авто-специализация не является транзитивной, он затрагивает толькоINLINABLE
функции импортированных и призвали в исходном коде изA
.В вашем примере, если вы переписываете экземпляр
Num
следующим образом:quxAdd
специально не импортируетсяMain
.Main
импортирует словарь экземпляровNum (Qux Int)
, и этот словарь содержитсяquxAdd
в записи для(+)
. Однако, несмотря на то, что словарь импортирован, содержимое, используемое в словаре, нет.plus
не вызываетquxAdd
, он использует функцию, сохраненную для(+)
записи в словаре экземпляраNum t
. Этот словарь устанавливается на сайте вызова (вMain
) компилятором.источник