Транзитивность авто-специализации в GHC
Из документы для GHC 7.6:
[Y] ou часто даже не нуждается в прагме SPECIALIZE. При компиляции модуля M оптимизатор GHC (с -O) автоматически рассматривает каждую перегруженную функцию верхнего уровня, объявленную в M, и специализируется на разных типах, в которых она вызывается в M. Оптимизатор также рассматривает каждую импортированную перегруженную INLINABLE функцию, и специализируется на разных типах, в которых он вызывается в M.
и
Кроме того, учитывая прагму 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
). Это ожидаемое поведение? Должен ли я ожидать, что (+)
будет специализированным транзитно с явной прагмой?
UPDATE
Документы для 7.8.2 не изменились, и поведение одинаковое, поэтому этот вопрос по-прежнему имеет значение.
Ответы
Ответ 1
Краткие ответы:
Ключевыми моментами вопроса, насколько я понимаю, являются следующие:
- "является транзитивной авто-специализацией?"
- Должен ли я ожидать, что (+) будет специализированно транзитивно с явной прагмой?
- (очевидно, предназначенный) Является ли это ошибкой GHC? Не соответствует ли он документации?
AFAIK, ответы Нет, в основном да, но есть и другие средства, а №
Встраивание кода и определение типа приложения - это компромисс между скоростью (временем выполнения) и размером кода. Уровень по умолчанию получает некоторое ускорение без раздувания кода. Выбор более исчерпывающего уровня остается на усмотрение программиста с помощью SPECIALISE
прагмы.
Объяснение:
Оптимизатор также рассматривает каждую импортированную перегруженную функцию INLINABLE и специализирует ее для разных типов, в которых она вызывается в M.
Предположим, что f
- это функция, тип которой включает переменную типа a
, ограниченную классом типа C a
. GHC по умолчанию специализирует f
применительно к типу приложения (подставляя a
для t
), если f
вызывается с этим типом приложения в исходном коде (a) любой функции в том же модуле или ( b) если f
отмечено INLINABLE
, то любой другой модуль, который импортирует f
из B
. Таким образом, автоматическая специализация не является транзитивной, она касается только INLINABLE
функций, импортированных и вызываемых в исходном коде a
.
В вашем примере, если вы перепишете экземпляр Num
следующим образом:
instance (Num r, Unbox r) => Num (Qux r) where
(+) = quxAdd
quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
-
quxAdd
не импортируется специально Main
. Main
импортирует словарь экземпляра Num (Qux Int)
, и этот словарь содержит quxAdd
в записи для (+)
. Однако, хотя словарь импортирован, содержимое, используемое в словаре, не является.
-
plus
не вызывает quxAdd
, он использует функцию, сохраненную для записи (+)
в словаре экземпляра Num t
. Этот словарь устанавливается на сайт вызова (в Main
) компилятором.