2012-07-02 5 views
17

solrize # 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는 전문 때 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 한 번 계산 및 공유 (정적 인수 한 번만 적용)된다. 느린 버전에서는 매번 다시 계산됩니다.

당신은 더 나은 버전을 생산하기 위해 추론이 줄을 따를 수

: 비용을 절단

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) 

:

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

그리고, 배열 융합을 사용하여, 당신은 조성 스타일의 형벌을 제거 할 수 있습니다 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

다른 누락 된 최적화 : 상수 (로그 10)로 나누는 것은 역수를 곱하여 대체해야합니다.

+0

조심하세요. 결과가 바뀔 수 있습니다. –

관련 문제