2012-07-02 7 views
17

solrize#haskellでこのコードの1つのバージョンについて質問があり、他のいくつかのケースを試してみて、何が起こっているのか不思議に思っていました。私のマシンでは、「高速」コードは約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は特殊なlog y/log x、とまったく同じものにlogBase x yを回すことができるであろうことを期待。ここで何が起こっているのですか?logBaseを使用する際の推奨方法は何ですか?

+3

ghcは、場合によっては「log 10」の一定の伝播を行っている可能性があります。可変ベースで測定してみてください。 –

+0

n.b. 'Double'の' Floating'インスタンスは、上の 'fooLogBase'のコメントアウトされた定義と同じように' logBase'を定義します。 – 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 
    } 
    } 
    } 
    } 

、log10のは、(静的引数は1回だけ適用されている)を1回計算され、共有されています。遅いバージョンでは、毎回再計算されます。

あなたも、より良いバージョンを生成するために推論のこのラインをたどることができます。

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

もう1つの不足している最適化:定数(log 10)で除算することは、逆数を掛けることで置き換える必要があります。

+0

注意してください。それは結果を変えることができます。 –

関連する問題