2012-07-02 27 views
17

solrize in #haskell ha fatto una domanda su una versione di questo codice e ho provato altri casi e mi chiedevo cosa stesse succedendo. Sulla mia macchina il codice "veloce" richiede ~ 1 secondo e il codice "lento" richiede ~ 1.3-1.5 (tutto è compilato con ghc -O2).Perché `logBase 10 x` è più lento di` log x/log 10`, anche se specializzato?

import Data.List 

log10 :: Double -> Double 
--log10 x = log x/log 10 -- fast 
--log10 = logBase 10 -- slow 
--log10 = barLogBase 10 -- fast 
--log10 = bazLogBase 10 -- fast 
log10 = fooLogBase 10 -- see below 

class Foo a where 
    fooLogBase :: a -> a -> a 

instance Foo Double where 
    --fooLogBase x y = log y/log x -- slow 
    fooLogBase x = let lx = log x in \y -> log y/lx -- fast 


barLogBase :: Double -> Double -> Double 
barLogBase x y = log y/log x 

bazLogBase :: Double -> Double -> Double 
bazLogBase x = let lx = log x in \y -> log y/lx 


main :: IO() 
main = print . foldl' (+) 0 . map log10 $ [1..1e7] 

I'd've speravano che GHC sarebbe in grado di trasformare logBase x y nella esattamente la stessa cosa di log y/log x, quando specializzata. Cosa sta succedendo qui e quale sarebbe il modo consigliato di utilizzare logBase?

+3

ghc potrebbe fare propagazione costante di 'log 10' in alcuni casi. Prova a misurare con una base variabile. –

+0

n.b. L'istanza 'Floating' per' Double' definisce 'logBase' in modo equivalente alla definizione commentata di' fooLogBase' sopra. – dave4420

+1

Sono tutti ugualmente veloci durante la compilazione con il backend LLVM. – leftaroundabout

risposta

22

Come sempre, guarda il Core.

veloce (1.563s) -

-- note: top level constant, referred to by specialized fooLogBase 

Main.main_lx :: GHC.Types.Double 
Main.main_lx = 
    case GHC.Prim.logDouble# 10.0 of { r -> 
    GHC.Types.D# r 
    } 

Main.main7 :: GHC.Types.Double -> GHC.Types.Double 
Main.main7 = 
    \ (y :: GHC.Types.Double) -> 
    case y of _ { GHC.Types.D# y# -> 
    case GHC.Prim.logDouble# y# of { r0 -> 
    case Main.main_lx of { GHC.Types.D# r -> 
    case GHC.Prim./## r0 r of { r1 -> 
    GHC.Types.D# r1 
    } 
    } 
    } 

lenta (2.013s)

-- simpler, but recomputes log10 each time 
Main.main7 = 
    \ (y_ahD :: GHC.Types.Double) -> 
    case y_ahD of _ { GHC.Types.D# x_aCD -> 
    case GHC.Prim.logDouble# x_aCD of wild1_aCF { __DEFAULT -> 
    case GHC.Prim.logDouble# 10.0 of wild2_XD9 { __DEFAULT -> 
    case GHC.Prim./## wild1_aCF wild2_XD9 of wild3_aCz { __DEFAULT -> 
    GHC.Types.D# wild3_aCz 
    } 
    } 
    } 
    } 

Nella versione veloce, log10 viene calcolato una sola volta e condivisi (l'argomento statico viene applicata una sola volta). Nella versione lenta viene ricalcolato ogni volta.

Puoi seguire questa linea di ragionamento per la produzione di versioni ancora meglio:

-- 1.30s 
lx :: Double 
lx = log 10 

log10 :: Double -> Double 
log10 y = log y/lx 

main :: IO() 
main = print . foldl' (+) 0 . map log10 $ [1..1e7] 

e, usando la fusione array, è possibile rimuovere la sanzione dello stile compositivo:

import qualified Data.Vector.Unboxed as V 

lx :: Double 
lx = log 10 

log10 :: Double -> Double 
log10 y = log y/lx 

main :: IO() 
main = print . V.sum . V.map log10 $ V.enumFromN 1 (10^7) 

Tagliare il costo di 3x

$ time ./A 
6.5657059080059275e7 

real 0m0.672s 
user 0m0.000s 
sys  0m0.000s 

Che è buono come scrivendolo a mano. Il seguente non offre alcun vantaggio rispetto alla versione scritta correttamente sopra.

lx :: Double 
lx = D# (GHC.Prim.logDouble# 10.0##) 

log10 :: Double -> Double 
log10 (D# y) = D# (case logDouble# y of r -> r /## d#) 
    where 
     D# d# = lx 

main :: IO() 
main = print . V.sum . V.map log10 $ V.enumFromN 1 (10^7) 
+0

Questo è veramente povero di ghc. Garantisce un biglietto – augustss

+2

GHC sembra considerare 'logBase # 10 'così a buon mercato da non farlo fluttuare, anche se qui conta davvero. E non ci sono regole speciali di riscrittura per i logaritmi (quindi nessuna piegatura costante). –

+7

Questo è un bug. Le varie funzioni elementari devono essere piegate o flottate costanti. – augustss

2

Un'altra ottimizzazione persa: la divisione per una costante (log 10) deve essere sostituita con la moltiplicazione per il reciproco.

+0

Attento. Questo può cambiare il risultato. –

Problemi correlati