简体   繁体   English

Haskell 中奇怪的执行时间

[英]Weird Execution Time in Haskell

I defined a memorized version of the factorial function.我定义了阶乘函数的记忆版本。 I observed that the second time I ran with the same parameter again, the execution time got greatly improved.我观察到第二次再次使用相同的参数运行时,执行时间得到了极大的改善。 But what confuses me is that both of them are much slower than the factorial function itself.但令我困惑的是,它们都比阶乘函数本身慢得多。 For example, one run of the following program例如,运行以下程序

import Text.Printf
import Control.Exception
import System.CPUTime

-- test function
fact :: Int -> Int
fact n = product [1..n]

mem :: (Int -> Int) -> Int -> Int
mem f = (map f [0..] !!)

mem_fact = mem fact

time :: IO t -> IO t
time a = do
    start <- getCPUTime
    v <- a
    end <- getCPUTime
    let diff = (fromIntegral (end - start)) / (10^12)
    printf "Computation time: %0.3f sec\n" (diff :: Double)
    return v

main = do
    putStrLn "Starting..."
    time $ fact 3000000 `seq` return ()
    time $ mem_fact 3000000 `seq` return ()
    time $ mem_fact 3000000 `seq` return ()
    putStrLn "Done."

produces the following output:产生以下输出:

Starting...
Computation time: 0.008 sec
Computation time: 0.621 sec
Computation time: 0.047 sec
Done.

Why is calling fact so much faster than mem_fact ?为什么调用factmem_fact Any explanations?有什么解释吗?

I agree with Daniel, much as I hate to (/s), that looking up the nth element in a linked list is slow and thus not the right way to memoize.我同意 Daniel 的观点,尽管我很讨厌 (/s),但在链表中查找第 n 个元素很慢,因此不是正确的记忆方法。 One solution is to use a map or trie, but since this has already been done we can just leverage one of the many trie memoization packages on hackage.一种解决方案是使用 map 或 trie,但由于这已经完成,我们可以仅利用 hackage 上的许多 trie memoization 包之一。

Cutting to the chase:切入正题:

  • fact takes ~ 300 us fact需要 ~ 300 us
  • Your list memoization takes ~ 1000 to 2000 us你的列表记忆需要大约1000 到 2000 us
  • Using MemoTrie results in runtimes of ~ 0.4 us使用 MemoTrie 导致运行时间约为0.4 us

This was determined by using your code and memotrie with Criterion:这是通过将您的代码和记忆与 Criterion 结合使用来确定的:

import Data.MemoTrie
import Criterion.Main

end :: Int
end = 300000

tmdMemo :: Int -> Int
tmdMemo x = memo2 go 1 x
 where
   go :: Int -> Int -> Int
   go acc 1 = acc
   go acc n = let new = n*acc in new `seq` memo2 go new (n-1)


-- test function
fact :: Int -> Int
fact n = product [1..n]

mem :: (Int -> Int) -> Int -> Int
mem f = (map f [0..] !!)

gaoMemo :: Int -> Int
gaoMemo = mem fact

main :: IO ()
main = defaultMain [ bench "Normal"   $ nf fact end
                   , bench "TMD-Memo" $ nf tmdMemo end
                   , bench "Gao-Memo" $ nf gaoMemo end
                   ]

With the results of:结果如下:

benchmarking Normal
time                 270.7 μs   (268.8 μs .. 272.7 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 271.7 μs   (270.4 μs .. 273.6 μs)
std dev              4.997 μs   (3.971 μs .. 6.812 μs)
variance introduced by outliers: 11% (moderately inflated)

benchmarking TMD-Memo
time                 379.3 ns   (376.2 ns .. 382.3 ns)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 381.5 ns   (378.0 ns .. 386.3 ns)
std dev              13.66 ns   (10.74 ns .. 17.59 ns)
variance introduced by outliers: 52% (severely inflated)

benchmarking Gao-Memo
time                 1.439 ms   (1.408 ms .. 1.469 ms)
                     0.996 R²   (0.994 R² .. 0.998 R²)
mean                 1.446 ms   (1.430 ms .. 1.467 ms)
std dev              63.31 μs   (53.75 μs .. 74.26 μs)
variance introduced by outliers: 31% (moderately inflated)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM