Przechodniość autospecjalizacji w GHC

392

Z dokumentacji GHC 7.6:

[T] często często nie potrzebujesz pragmy SPECJALIZACJI. Podczas kompilacji modułu M optymalizator GHC (z -O) automatycznie uwzględnia każdą przeciążoną funkcję najwyższego poziomu zadeklarowaną w M i specjalizuje ją dla różnych typów, w których jest wywoływany w M. Optymalizator bierze również pod uwagę każdą importowaną funkcję przeciążenia INLINABLE, i specjalizuje się w różnych typach nazwanych w M.

i

Co więcej, biorąc pod uwagę pragmat SPECIALIZE dla funkcji f, GHC automatycznie utworzy specjalizacje dla funkcji przeciążonych klasą typu wywoływanych przez f, jeśli są one w tym samym module co pragma SPECIALIZE, lub jeśli są NIEZŁĄCZNE; i tak dalej, tranzytowo.

Więc GHC powinien automatycznie specjalizować niektóre / większość / wszystkie (?) Funkcje oznaczone INLINABLE bez pragmy, a jeśli użyję wyraźnej pragmy, specjalizacja jest przechodnia. Moje pytanie brzmi: czy przechodzenie na automatyczną specyfikację jest przechodnie?

Oto mały przykład:

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 specjalizuje się w wywołaniu plus, ale nie specjalizuje się (+)w Qux Numinstancji, która zabija wydajność.

Jednak wyraźna pragma

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

skutkuje specjalizacją przechodnią, jak wskazują dokumenty, więc (+)jest wyspecjalizowany, a kod jest 30 razy szybszy (oba kompilowane -O2). Czy to oczekiwane zachowanie? Czy powinienem oczekiwać, (+)że będę specjalizował się wyłącznie w transporcie z wyraźną pragmą?


AKTUALIZACJA

Dokumenty dla wersji 7.8.2 nie uległy zmianie, a zachowanie jest takie samo, więc to pytanie jest nadal aktualne.

crockeea
źródło
33
Nie znam odpowiedzi, ale wygląda na to, że może być związana z: ghc.haskell.org/trac/ghc/ticket/5928 Prawdopodobnie warto otworzyć nowy bilet lub dodać tam informacje, jeśli uważasz, że jest to związane z 5928
jberryman
6
@jberryman Wydaje się, że istnieją dwie różnice między tym biletem a moim pytaniem: 1) W bilecie ekwiwalent nieplus został oznaczony jako INLINABLE i 2) simonpj wskazał, że było trochę inlinizacji z kodem biletu, ale rdzeń z mój przykład pokazuje, że żadna z funkcji nie została wstawiona (w szczególności nie mogłem pozbyć się drugiego konstruktora, w przeciwnym razie wstawiono GHC). Foo
crockeea
5
Ah, dobrze. Co stanie się, gdy zdefiniujesz plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2, aby LHS został w pełni zastosowany na stronie wywoławczej? Czy zaczyna się inline, a potem zaczyna specjalizacja?
jberryman
3
@jberryman Funny powinieneś zapytać. Byłem na tej drodze z tym pytaniem, które doprowadziło do tego raportu śledzenia . Pierwotnie miałem wezwanie do pluspełnego zastosowania specjalnie z powodu tych linków, ale w rzeczywistości uzyskałem mniej specjalizacji: wezwanie do plusnie było również specjalizowane. Nie mam na to wytłumaczenia, ale zamierzałem zostawić to na inne pytanie lub mam nadzieję, że zostanie rozwiązane w odpowiedzi na to pytanie.
crockeea
11
From ghc.haskell.org/trac/ghc/wiki/ReportABug : „W razie wątpliwości zgłoś błąd.” Nie powinieneś czuć się źle, zwłaszcza, że ​​wystarczająca liczba naprawdę doświadczonych haskellerów tutaj nie wie, jak odpowiedzieć na twoje pytanie. Takie przypadki testowe są prawdopodobnie bardzo cenne dla twórców GHC. W każdym razie, powodzenia! Zaktualizowałem pytanie, jeśli złożysz bilet
jberryman

Odpowiedzi:

4

Krótkie odpowiedzi:

Kluczowe punkty pytania, tak jak je rozumiem, są następujące:

  • „czy automatyczna specjalizacja jest przechodnia?”
  • Czy powinienem oczekiwać tylko (+), że będę się specjalizował w transporcie z wyraźną pragmą?
  • (najwyraźniej zamierzony) Czy to błąd GHC? Czy jest to niezgodne z dokumentacją?

AFAIK, odpowiedzi są nie, przeważnie tak, ale są też inne środki i nie.

Wstawianie kodu i specjalizacja aplikacji typu to kompromis między szybkością (czasem wykonania) a rozmiarem kodu. Poziom domyślny uzyskuje pewne przyspieszenie bez rozszerzania kodu. Wybór bardziej wyczerpującego poziomu należy do programisty według SPECIALISEpragmy.

Wyjaśnienie:

Optymalizator rozważa również każdą zaimportowaną przeciążoną funkcję INLINABLE i specjalizuje ją dla różnych typów, w których jest wywoływana w M.

Załóżmy, że fjest to funkcja, której typ zawiera zmienną typu aograniczoną przez klasę typu C a. GHC domyślnie specjalizuje się fw odniesieniu do aplikacji typu (zastępując ana t) jeżeli fjest wywoływana z tej aplikacji typu w kodzie źródłowym (A) dowolnej funkcji w jednym module, lub (b) jeżeli fjest oznaczony INLINABLE, a następnie każdy inny moduł importu f z B. Zatem, auto-specjalizacja nie jest przechodnia, to tylko dotyka INLINABLEfunkcje importowane i wezwał do w kodzie źródłowym z A.

W twoim przykładzie, jeśli przepisujesz instancję w Numnastępujący sposób:

instance (Num r, Unbox r) => Num (Qux r) where
    (+) = quxAdd

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddnie jest specjalnie importowany przez Main. Mainimportuje słownik instancji Num (Qux Int), a ten słownik zawiera quxAddw rekordzie dla (+). Jednak chociaż słownik jest importowany, zawartość używana w słowniku nie jest.
  • plusnie wywołuje quxAdd, używa funkcji zapisanej dla (+)rekordu w słowniku instancji Num t. Słownik ten jest ustawiany Mainprzez kompilator na stronie wywołania (in ).
Diego E. Alonso-Blas
źródło