it-swarm.com.ru

Транзитивность авто-специализации в GHC

От документы для GHC 7.6:

[Y] Вам часто даже не нужна прагма SPECIALIZE. При компиляции модуля M оптимизатор GHC (с -O) автоматически рассматривает каждую перегруженную функцию верхнего уровня, объявленную в M, и специализирует ее для различных типов, в которых она вызывается в M. Оптимизатор также учитывает каждую импортированную перегруженную функцию INLINABLE, и специализирует его для разных типов, в которых он называется в М.

а также

Более того, учитывая прагму SPECIALIZE для функции f, GHC автоматически создает специализации для любых функций, перегруженных классами типов, вызываемых f, если они находятся в том же модуле, что и прагма SPECIALIZE, или если они являются INLINABLE; и так далее, переходно.

Так что GHC должен автоматически специализировать некоторые/большинство/все (?) функции, отмеченные INLINABLE без прагмы, и если я использую явную прагму, специализация будет транзитивно. Мой вопрос: auto - специализация транзитивна?

В частности, вот небольшой пример:

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, но не не специализирует (+) в экземпляре QuxNum, что снижает производительность.

Тем не менее, явная прагма

{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}

приводит к транзитивной специализации, как указывают документы, поэтому (+) является специализированным и код работает в 30 раз быстрее (оба компилируются с -O2). Это ожидаемое поведение? Стоит ли ожидать, что (+) будет специализироваться транзитивно с явной прагмой?


ОБНОВИТЬ

Документы по 7.8.2 не изменились, и поведение остается прежним, поэтому этот вопрос по-прежнему актуален.

390
crockeea

Короткие ответы:

Ключевыми моментами вопроса, насколько я понимаю, являются следующие:

  • "Является ли авто-специализация переходной?"
  • Стоит ли ожидать, что (+) будет специализироваться транзитивно с явной прагмой?
  • (очевидно, предназначен) Это ошибка 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) компилятором.
3
Diego E. Alonso-Blas