ho questi tipi di base nel mio codice:Haskell Performance di Esempio
newtype Foo (m :: Factored) = Foo Int64 deriving (NFData)
foo :: forall m . (Fact m) => Foo m -> Foo m
class T t where t :: (Fact m) => t m -> t m
instance T Foo where t = foo
newtype Bar t (m :: Factored) = Bar (t m) deriving (NFData)
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
(ignorare Fact
e Factored
per il momento). Sto analizzando il codice a diversi livelli, confrontando le prestazioni di foo
, t
e bar
. Nei benchmark, t = foo
e bar
si applica solo t
tramite newtype
. Quindi il loro runtime dovrebbe essere essenzialmente identico, ma criterion segnala che foo
prende 9,2ns, t
prende circa il doppio di quello a 17,45 ns, e il numero bar
prende un 268,1 n enorme.
Ho sperimentato con l'aggiunta di INLINABLE
e anche un pragma SPECIALIZE
, ma non stanno aiutando. Voglio credere che GHC abbia qualche sintassi/ottimizzazione/ecc magico che può essere coerentemente applicato per risolvere questi tipi di problemi di prestazioni. Ad esempio, ho visto casi in cui scrivere codice in stile point-free ha implicazioni drammatiche sulle prestazioni.
Il codice completo può essere trovato here. Prometto che non è intimidatorio. I moduli sono:
- Foo: definisce
Foo
,foo
eT
- Bar: definisce
Bar
ebar
- FooBench: definisce un punto di riferimento per
foo
- TBench: definisce un punto di riferimento per
t
- BarBench: definisce un punto di riferimento per
bar
- Principale: gestisce i tre parametri di riferimento
- scomposto: definisce
Fact
eFactored
utilizzando singletons
maggior parte dei moduli sono minuscole; Ho definito i tre benchmark in file separati in modo da poter esaminare il loro nucleo. Ho generato il core per i tre moduli *Bench
e li ho allineati il meglio che potevo. Sono solo ~ 250 linee ciascuno, e le prime ~ 200 righe sono identiche, fino alla ridenominazione. Il problema è che non so cosa fare delle ultime 50 linee. L'(core) diff per FooBench
vs TBench
è here, il diff per TBench
vs BarBench
è here, e il diff per FooBench
vs BarBench
è here.
Solo alcune delle domande che ho:
Ad alto livello, qual è la differenza essenziale tra i file di base? Sto cercando qualcosa come "Qui puoi vedere che GHC non sta mettendo in riga la chiamata a
x
". Cosa dovrei cercare?Cosa si può fare per far funzionare tutti e tre i benchmark in circa 9,2ns? Ottimizzazioni GHC?
INLINE
/INLINABLE
pragmi?SPECIALIZE
pragmi che mi sono perso?(Non è consentito specializzarsi perF128::Factored
, nella mia libreria reale questo valore può essere reificato in fase di esecuzione.) Limitazione/ritardo dell'integrazione in una particolare fase?
Anche se sto cercando una soluzione reale per rendere i benchmark tutto veloce, è possibile che i trucchi che lavorano per questo esempio non scala alla mia vera biblioteca. Di conseguenza, sto anche cercando la spiegazione "di alto livello" sul perché una particolare tecnica dovrebbe funzionare.
Questo può essere utile: http://stackoverflow.com/questions/6121146/reading-ghc-core –
La mia _guess_ è che la differenza che stai osservando è il costo di passare i dizionari 'Fact' e' T' circa –
@BenjaminHodgson Mi piacerebbe davvero ottenere la specializzazione fino in fondo allo stack in modo che non ci siano dizionari coinvolti una volta monomorfizzati al massimo livello. Qualche idea su come farlo accadere? – crockeea