为什么`(map digitToInt)。秀`这么快?

时间:2011-01-30 04:03:49

标签: performance haskell ghc digits

将非负Integer转换为其数字列表通常是这样做的:

import Data.Char

digits :: Integer -> [Int]
digits = (map digitToInt) . show

我试图找到一种更直接的方式来执行任务,而不涉及字符串转换,但我无法提出更快的内容。

到目前为止我一直在尝试的事情:

基线:

digits :: Int -> [Int]
digits = (map digitToInt) . show

从StackOverflow上的另一个问题得到这个:

digits2 :: Int -> [Int]
digits2 = map (`mod` 10) . reverse . takeWhile (> 0) . iterate (`div` 10)

尝试自己动手:

digits3 :: Int -> [Int]
digits3 = reverse . revDigits3

revDigits3 :: Int -> [Int]
revDigits3 n = case divMod n 10 of
               (0, digit) -> [digit]
               (rest, digit) -> digit:(revDigits3 rest)

这一启发灵感来自showInt中的Numeric

digits4 n0 = go n0 [] where
    go n cs
        | n < 10    =  n:cs
        | otherwise =  go q (r:cs)
        where
        (q,r) = n `quotRem` 10

现在是基准。注意:我正在使用filter强制进行评估。

λ>:set +s
λ>length $ filter (>5) $ concat $ map (digits) [1..1000000]
2400000
(1.58 secs, 771212628 bytes)

这是参考。现在是digits2

λ>length $ filter (>5) $ concat $ map (digits2) [1..1000000]
2400000
(5.47 secs, 1256170448 bytes)

3.46 倍。

λ>length $ filter (>5) $ concat $ map (digits3) [1..1000000]
2400000
(7.74 secs, 1365486528 bytes)

digits3 4.89 时间较慢。只是为了好玩,我尝试只使用revDigits3并避开reverse

λ>length $ filter (>5) $ concat $ map (revDigits3) [1..1000000]
2400000
(8.28 secs, 1277538760 bytes)

奇怪的是,这甚至更慢, 5.24 倍慢。

最后一个:

λ>length $ filter (>5) $ concat $ map (digits4) [1..1000000]
2400000
(16.48 secs, 1779445968 bytes)

10.43 时间较慢。

我的印象是只使用算术和缺点会胜过涉及字符串转换的任何内容。显然,有些东西我无法掌握。

那么诀窍呢?为什么digits如此之快?

我正在使用GHC 6.12.3。

2 个答案:

答案 0 :(得分:30)

答案 1 :(得分:12)

回答“为什么rem而不是mod?”这个问题。在评论中。在处理正值rem x y === mod x y时,唯一需要注意的是性能:

> import Test.QuickCheck
> quickCheck (\x y -> x > 0 && y > 0 ==> x `rem` y == x `mod` y)

那么性能如何呢?除非你有充分的理由不(并且懒惰不是一个好理由,也不是不知道Criterion)然后使用一个好的基准工具,我使用Criterion:

$ cat useRem.hs 
import Criterion
import Criterion.Main

list :: [Integer]
list = [1..10000]

main = defaultMain
        [ bench "mod" (nf (map (`mod` 7)) list)
        , bench "rem" (nf (map (`rem` 7)) list)
        ]

运行此展示显示rem明显优于mod(使用-O2编译):

$ ./useRem 
...
benchmarking mod
...
mean: 590.4692 us, lb 589.2473 us, ub 592.1766 us, ci 0.950

benchmarking rem
...
mean: 394.1580 us, lb 393.2415 us, ub 395.4184 us, ci 0.950