2015-03-23 7 views
9

Ho un codice che esegue l'analisi dei file in base alle regole specificate. L'intera analisi si svolge in una monade che è una pila di ReaderT/STTrans/ErrorT.Quando si generalizza la monade, le prestazioni diminuiscono di quasi il 50%

type RunningRule s a = ReaderT (STRef s LocalVarMap) (STT s (ErrorT String Identity)) a 

Perché sarebbe utile per eseguire alcuni IO nel codice (ad esempio, per interrogare i database esterni), ho pensato di generalizzare l'analisi, in modo che potesse funzionare sia in monade identità o IO base, a seconda la funzionalità che vorrei. La situazione è cambiata la firma di:

type RunningRule s m a = ReaderT (STRef s LocalVarMap) (STT s (ErrorT String m)) a 

Dopo aver modificato le firme tipo appropriato (e con alcune estensioni per aggirare i tipi) ho corso di nuovo in monade Identità ed era ~ 50% più lento. Anche se sostanzialmente nulla è cambiato, è molto più lento. Questo comportamento normale? C'è un modo semplice per rendere questo più veloce? (ad esempio, combinando lo stack ErrorT e ReaderT (e possibilmente STT) in un trasformatore monad?)

Per aggiungere un campione di codice - è una cosa che sulla base di un input analizzato (fornito in linguaggio di tipo C) costruisce un parser. Il codice è simile al seguente:

compileRule :: forall m. (Monad m, Functor m) => 
     -> [Data -> m (Either String Data)] -- For tying the knot 
     -> ParsedRule -- This is the rule we are compiling 
     -> Data -> m (Either String Data) -- The real parsing 
compileRule compiled (ParsedRule name parsedlines) = 
    \input -> runRunningRule input $ do 
     sequence_ compiledlines 
    where 
     compiledlines = map compile parsedlines 
     compile (Expression expr) = compileEx expr >> return() 
     compile (Assignment var expr) = 
     ... 
     compileEx (Function "check" expr) = do 
      value <- expr 
      case value of 
       True -> return() 
       False -> fail "Check failed" 
      where 
       code = compileEx expr 
+1

Hai compilato con ottimizzazioni e il profilo con criterio? Quali sono stati i passaggi di profilazione che hai preso per trovare questo rallentamento? – bheklilr

+1

Compilato con ottimizzazioni, abilitato il profiling ed eseguito con + RTS -p, ha confrontato gli output - il risultato suggeriva principalmente nulla (a parte che probabilmente ha qualcosa a che fare con le monadi). Ho usato il "tempo" generale per controllare le prestazioni, i miei dati di test impiegavano 2 secondi con "Identità hardcoded" e 3 secondi quando permettevo di impostare la monade Identity come "parametro". – ondra

+0

"e utilizzando alcune estensioni per aggirare i tipi" <- potrebbe indicare che qualcosa è cambiato di conseguenza. L'esempio di codice esteso in questione potrebbe aiutare a trovare prima una spiegazione. –

risposta

12

Questo non è così insolito, no. Dovresti provare a usare i prefissi SPECIALIZE per specializzarli a Identity e forse anche a IO. Utilizzare -ddump-simpl e osservare gli avvertimenti sul lato sinistro della mano che è troppo complicato. Quando la specializzazione non avviene come dovrebbe, GHC finisce col passare in rassegna dizionari di typeclass in fase di runtime. Questo è intrinsecamente un po 'inefficiente, ma soprattutto impedisce a GHC di inlining i metodi di classe per consentire un'ulteriore semplificazione.

+0

Ho provato ad aggiungerlo, sembra che abbia funzionato nel senso che ddump-simpl sembra mostrare che è davvero specializzato (ha dovuto aggiungere anche INLINABLE). Ha avuto un effetto trascurabile sulle prestazioni. Il fatto è che sto costruendo un sacco di espressioni lambda in esecuzione in questa monade, forse non si specializzano? – ondra

+1

Penso che dovremmo effettivamente confrontare il codice originale con quello generalizzato. – dfeuer

+0

@ondra, volevo farti un ping su quell'ultimo commento. – dfeuer