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


392

З документів для GHC 7.6:

[Y] часто навіть не потрібна СПЕЦІАЛІЗАЦІЯ в першу чергу. Під час компіляції модуля M оптимізатор GHC (з -O) автоматично враховує кожну функцію перевантаженого верхнього рівня, заявлену в M, і спеціалізує її для різних типів, при яких вона викликається в М. Оптимізатор також враховує кожну імпортовану НЕМАГНУЮ функцію перевантаження, і спеціалізує його для різних типів, при яких він називається в М.

і

Більше того, даючи прагму СПЕЦІАЛІЗАЦІЇ для функції f, GHC автоматично створить спеціалізацію для будь-яких функцій, перевантажених типом класу, викликаних f, якщо вони є в тому ж модулі, що і прагма SPECIALIZE, або якщо вони НЕВІДОМНІ; і так далі, перехідно.

Таким чином, GHC повинен автоматично спеціалізувати деякі / більшість / всі (?) Функції, позначені INLINABLE без прагми, і якщо я використовую явну прагму, спеціалізація є транзитивною. Моє питання: це авто -specialization транзитивним?

Зокрема, ось невеликий приклад:

Основні.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 не змінилися, і поведінка однакова, тому це питання все ще актуальне.


33
Я не знаю відповіді, але це здається, що це може бути пов’язано з: ghc.haskell.org/trac/ghc/ticket/5928 Можливо, варто відкрити новий квиток або додати туди свою інформацію, якщо ви думаєте, що це, ймовірно, пов’язано з 5928
jberryman

6
@jberryman Там , як видається, дві відмінності між цим квитком і моїм питанням: 1) У квитку, еквівалент plusбув НЕ відзначений як INLINABLE і 2) simonpj показав , що існує деякий вбудовування відбувається з кодом квитка, але ядро з мій приклад показує, що жодна з функцій не була вбудована (зокрема, я не міг позбутися другого Fooконструктора, інакше GHC вбудований матеріал).
crockeea

5
ах, добре. Що відбувається, коли ви визначаєте plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2, щоб LHS повністю застосовувався на сайті виклику? Це стає впорядкованим, а потім починається спеціалізація?
jberryman

3
@jberryman Смішно, що вам слід запитати. Я пішов по дорозі з цим питанням, яке призвело до цього звіту про простеження . Спочатку я мав заклик plusповністю застосуватись саме через ці посилання, але насправді я отримав меншу спеціалізацію: дзвінок також plusне був спеціалізованим. Я не маю цього пояснення, але мав намір залишити його для іншого питання, або сподіваюся, що це буде вирішено у відповіді на це.
crockeea

11
Від ghc.haskell.org/trac/ghc/wiki/ReportABug : "Якщо ви сумніваєтеся, просто повідомте про свою помилку." Вам не слід почувати себе погано, тим більше, що достатня кількість справді досвідчених хакелерів тут не знає, як відповісти на ваше запитання. Такі приклади тестів, ймовірно, справді цінні для розробників GHC. Все одно удачі! Оновлено питання, якщо ви
подаєте

Відповіді:


4

Короткі відповіді:

Основними питаннями, наскільки я їх розумію, є наступні:

  • "чи є транзитивна автоспеціалізація?"
  • Чи варто лише сподіватися, що (+) буде спеціалізовано транзитивно з явною прагмою?
  • (мабуть, призначено) Це помилка GHC? Це суперечить документації?

AFAIK, відповіді - Ні, здебільшого так, але є й інші засоби.

Спеціалізація додатків для введення коду та типу - це компроміс між швидкістю (часом виконання) та розміром коду. Рівень за замовчуванням отримує деяке прискорення без здуття коду. Вибір більш вичерпного рівня залишається на розсуд програміста за допомогою SPECIALISEпрагми.

Пояснення:

Оптимізатор також враховує кожну імпортовану функцію INLINABLE перевантаженої та спеціалізує її для різних типів, при яких вона викликається у M.

Припустимо f, це функція, тип якої включає змінну типу, aобмежену класом типу C a. GHC за замовчуванням спеціалізується fпо відношенню до типу програми (замінює aдля t) , якщо fвикликається з цим типом програми у вихідному коді (а) будь-якої функції в тому ж самому модулі, або (б) , якщо 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. Цей словник встановлюється на сайті виклику (in Main) компілятором.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.