2012-07-02 32 views
17

在#haskell問了一個關於這個代碼的一個版本的問題,我試了一些其他的例子,並想知道發生了什麼。在我的機器上,「快速」代碼需要大約1秒,而「慢」代碼需要大約1.3-1.5(所有內容均編譯爲ghc -O2)。爲什麼`logBase 10 x`比'log x/log 10`慢,哪怕是專門的?

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希望GHC將能夠專門何時打開logBase x y成一模一樣的東西log y/log x。這裏發生了什麼,以及使用logBase的推薦方式是什麼?

+3

ghc在某些情況下可能會不斷傳播'log 10'。嘗試使用可變基數進行測量。 –

+0

n.b. Double的'Floating'實例定義了'logBase'等同於上面'fooLogBase'的註釋掉的定義。 – dave4420

+1

當您使用LLVM後端進行編譯時,它們同樣快速。 – leftaroundabout

回答

22

一如既往,看看核心。

快速(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 
    } 
    } 
    } 

慢速(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 
    } 
    } 
    } 
    } 

在快速版本,日誌10被計算一次,並且共享(靜態參數一次僅施加)。在慢版本中,每次都會重新計算。

您可以按照這樣的推理,以產生更好的版本:

-- 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] 

,並使用陣列的融合,可以去除構圖風格的處罰:

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) 

切割成本通過3x

$ time ./A 
6.5657059080059275e7 

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

這是一樣好手寫它。以下提供的正確書面版本沒有任何好處。

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

ghc真的很差。認股權證。 – augustss

+2

GHC似乎認爲'logBase#10'非常便宜,它不會浮出來,儘管它在這裏確實很重要。對數沒有特殊的重寫規則(所以沒有常數摺疊)。 –

+7

這是一個錯誤。各種基本功能需要不斷摺疊或浮動。 – augustss

2

另一個錯過的優化:除以常數(log 10)應該用乘以倒數來代替。

+0

小心。這可以改變結果。 –

相關問題