Transitività della specializzazione automatica in GHC


392

Dai documenti per GHC 7.6:

[Y] spesso non hai nemmeno bisogno del pragma SPECIALIZE in primo luogo. Durante la compilazione di un modulo M, l'ottimizzatore di GHC (con -O) considera automaticamente ogni funzione di sovraccarico di livello superiore dichiarata in M ​​e lo specializza per i diversi tipi in cui viene chiamato in M. L'ottimizzatore considera anche ogni funzione di sovraccarico INLINABLE importata, e lo specializza per i diversi tipi in cui viene chiamato in M.

e

Inoltre, dato un pragma SPECIALIZE per una funzione f, GHC creerà automaticamente specializzazioni per tutte le funzioni di tipo sovraccarico di classe chiamate da f, se si trovano nello stesso modulo del pragma SPECIALIZE o se sono INLINABILI; e così via, in modo transitorio.

Quindi GHC dovrebbe specializzare automaticamente alcune / quasi / tutte (?) Funzioni contrassegnate INLINABLE senza un pragma, e se uso un pragma esplicito, la specializzazione è transitiva. La mia domanda è: l' auto- specializzazione è transitiva?

Nello specifico, ecco un piccolo esempio:

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 è specializzata la chiamata a plus, ma non senza specializzarsi (+)nel Qux Numcaso che uccide le prestazioni.

Tuttavia, un pragma esplicito

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

si traduce in specializzazione transitiva come indicano i documenti, quindi (+)è specializzata e il codice è 30 volte più veloce (entrambi compilati -O2). Questo comportamento è previsto? Dovrei aspettarmi (+)di essere specializzato solo in modo transitivo con un pragma esplicito?


AGGIORNARE

I documenti per 7.8.2 non sono cambiati e il comportamento è lo stesso, quindi questa domanda è ancora rilevante.


33
Non conosco la risposta ma sembra che potrebbe essere correlato a: ghc.haskell.org/trac/ghc/ticket/5928 Probabilmente vale la pena aprire un nuovo biglietto o aggiungere le tue informazioni lì se pensi che siano probabilmente correlate a 5928
jberryman,

6
@jberryman Sembrano esserci due differenze tra quel biglietto e la mia domanda: 1) Nel biglietto, l'equivalente di nonplus era contrassegnato come INLINABILE e 2) simonpj indicava che c'era qualche allineamento in corso con il codice del biglietto, ma il nucleo di il mio esempio mostra che nessuna delle funzioni era incorporata (in particolare, non potevo liberarmi del secondo costruttore, altrimenti roba incorporata in GHC). Foo
crockeea,

5
Ah ok. Cosa succede quando si definisce plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2, in modo che l'LHS sia pienamente applicato nel sito di chiamata? Viene incorporato e quindi inizia la specializzazione?
jberryman,

3
@jberryman Divertente che dovresti chiedere. Sono stato su questa strada con questa domanda che ha portato a questo rapporto trac . Inizialmente avevo la chiamata a essere pluspienamente applicata in modo specifico a causa di quei collegamenti, ma in realtà avevo meno specializzazione: la chiamata a plusnon era nemmeno specializzata. Non ho spiegazioni per questo, ma intendevo lasciarlo per un'altra domanda, o spero che si sarebbe risolto in una risposta a questa.
crockeea,

11
Da ghc.haskell.org/trac/ghc/wiki/ReportABug : "In caso di dubbi, segnala il tuo bug". Non dovresti sentirti male, soprattutto perché un numero sufficiente di haskeller con esperienza qui non sa come rispondere alla tua domanda. Casi di test come questo sono probabilmente molto utili per gli sviluppatori GHC. Comunque buona fortuna! Aggiornata la domanda se presenti un biglietto
jberryman,

Risposte:


4

Risposte brevi:

I punti chiave della domanda, per quanto li capisco, sono i seguenti:

  • "è l'auto-specializzazione transitiva?"
  • Dovrei aspettarmi che (+) sia specializzato in modo transitivo con un pragma esplicito?
  • (apparentemente inteso) È un bug di GHC? È incompatibile con la documentazione?

AFAIK, le risposte sono No, principalmente sì ma ci sono altri mezzi, e No.

L'integrazione del codice e la specializzazione dell'applicazione del tipo sono un compromesso tra velocità (tempo di esecuzione) e dimensioni del codice. Il livello predefinito ottiene un po 'di accelerazione senza gonfiare il codice. La scelta di un livello più esauriente è lasciata alla discrezione del programmatore tramite SPECIALISE pragma.

Spiegazione:

L'ottimizzatore considera anche ogni funzione sovraccaricata INLINABILE importata e la specializza per i diversi tipi in cui viene chiamata in M.

Supponiamo che funa funzione il cui tipo includa una variabile di tipo avincolata da una classe di tipo C a. GHC per impostazione predefinita è specializzata frispetto a un'applicazione di tipo (in sostituzione adi t) se fviene chiamata con tale applicazione di tipo nel codice sorgente di (a) qualsiasi funzione nello stesso modulo, o (b) se fè contrassegnata INLINABLE, quindi qualsiasi altro modulo che importa f da B. Pertanto, la specializzazione automatica non è transitiva, ma tocca solo le INLINABLEfunzioni importate e richieste nel codice sorgente diA .

Nel tuo esempio, se riscrivi l'istanza di Numcome segue:

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

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddnon è specificamente importato da Main. Mainimporta il dizionario di istanza di Num (Qux Int)e questo dizionario contiene quxAddnel record per(+) . Tuttavia, sebbene il dizionario sia importato, i contenuti utilizzati nel dizionario non lo sono.
  • plusnon chiama quxAdd, utilizza la funzione memorizzata per il (+)record nel dizionario dell'istanza di Num t. Questo dizionario è impostato nel sito di chiamata (in Main) dal compilatore.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.