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
Num
instancji, 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.
plus
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
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?plus
pełnego zastosowania specjalnie z powodu tych linków, ale w rzeczywistości uzyskałem mniej specjalizacji: wezwanie doplus
nie 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.Odpowiedzi:
Krótkie odpowiedzi:
Kluczowe punkty pytania, tak jak je rozumiem, są następujące:
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
SPECIALISE
pragmy.Wyjaśnienie:
Załóżmy, że
f
jest to funkcja, której typ zawiera zmienną typua
ograniczoną przez klasę typuC a
. GHC domyślnie specjalizuje sięf
w odniesieniu do aplikacji typu (zastępująca
nat
) jeżelif
jest wywoływana z tej aplikacji typu w kodzie źródłowym (A) dowolnej funkcji w jednym module, lub (b) jeżelif
jest oznaczonyINLINABLE
, a następnie każdy inny moduł importuf
zB
. Zatem, auto-specjalizacja nie jest przechodnia, to tylko dotykaINLINABLE
funkcje importowane i wezwał do w kodzie źródłowym zA
.W twoim przykładzie, jeśli przepisujesz instancję w
Num
następujący sposób:quxAdd
nie jest specjalnie importowany przezMain
.Main
importuje słownik instancjiNum (Qux Int)
, a ten słownik zawieraquxAdd
w rekordzie dla(+)
. Jednak chociaż słownik jest importowany, zawartość używana w słowniku nie jest.plus
nie wywołujequxAdd
, używa funkcji zapisanej dla(+)
rekordu w słowniku instancjiNum t
. Słownik ten jest ustawianyMain
przez kompilator na stronie wywołania (in ).źródło