Performance 为什么是";“更好”;数字列表功能更慢?

Performance 为什么是";“更好”;数字列表功能更慢?,performance,haskell,Performance,Haskell,我在玩ProjectEuler#34,我写了以下函数: import Data.Time.Clock.POSIX import Data.Char digits :: (Integral a) => a -> [Int] digits x | x < 10 = [fromIntegral x] | otherwise = let (q, r) = x `quotRem` 10 in (fromIntegral r) : (digits q) digitsBy

我在玩ProjectEuler#34,我写了以下函数:

import Data.Time.Clock.POSIX
import Data.Char

digits :: (Integral a) => a -> [Int]
digits x
    | x < 10 = [fromIntegral x]
    | otherwise = let (q, r) = x `quotRem` 10 in (fromIntegral r) : (digits q)

digitsByShow :: (Integral a, Show a) => a -> [Int]
digitsByShow = map (\x -> ord x - ord '0') . show
使用
ghc-O
编译后,
数字
始终需要0.5秒,而
digitsByShow
始终需要0.3秒。为什么会这样?为什么整数运算中的函数较慢,而字符串比较中的函数较快


我问这个问题是因为我来自Java和类似语言的编程,其中生成数字的
%10
技巧比“convert to String”方法快得多。我一直无法理解这样一个事实:转换为字符串可能会更快。

这是我能想到的最好的方法

digitsV2 :: (Integral a) => a -> [Int]
digitsV2 n = go n []
    where
      go x xs
          | x < 10    = fromIntegral x : xs
          | otherwise = case quotRem x 10 of
                  (q,r) -> go q (fromIntegral r : xs)
还是用箱子

| otherwise = case quotRem x 10 of
                (q,r) -> fromIntegral r : digits q
这样做会将
位数降低到323.5毫秒

编辑:不使用标准的时间

数字=464.3毫秒

Digitstrict=328.2毫秒

数字显示=259.2毫秒

数字V2=252.5毫秒

注意:Criteria软件包衡量软件性能。

让我们研究一下为什么更快

我运行了三次ghc:

ghc -O2 -ddump-simpl digits.hs >digits.txt
ghc -O2 -ddump-simpl digitsV2.hs >digitsV2.txt
ghc -O2 -ddump-simpl show.hs >show.txt
数字。hs

show.hs

如果您想查看完整的txt文件,我将它们放在ideone上(而不是在此处粘贴10000个字符的转储):


如果我们仔细查看
digits.txt
,似乎这是相关部分:

lvl_r1qU = __integer 10

Rec {
Main.$w$sdigits [InlPrag=[0], Occ=LoopBreaker]
  :: Integer -> (# Int, [Int] #)
[GblId, Arity=1, Str=DmdType <S,U>]
Main.$w$sdigits =
  \ (w_s1pI :: Integer) ->
    case integer-gmp-1.0.0.0:GHC.Integer.Type.ltInteger#
           w_s1pI lvl_r1qU
    of wild_a17q { __DEFAULT ->
    case GHC.Prim.tagToEnum# @ Bool wild_a17q of _ [Occ=Dead] {
      False ->
        let {
          ds_s16Q [Dmd=<L,U(U,U)>] :: (Integer, Integer)
          [LclId, Str=DmdType]
          ds_s16Q =
            case integer-gmp-1.0.0.0:GHC.Integer.Type.quotRemInteger
                   w_s1pI lvl_r1qU
            of _ [Occ=Dead] { (# ipv_a17D, ipv1_a17E #) ->
            (ipv_a17D, ipv1_a17E)
            } } in
        (# case ds_s16Q of _ [Occ=Dead] { (q_a11V, r_X12h) ->
           case integer-gmp-1.0.0.0:GHC.Integer.Type.integerToInt r_X12h
           of wild3_a17c { __DEFAULT ->
           GHC.Types.I# wild3_a17c
           }
           },
           case ds_s16Q of _ [Occ=Dead] { (q_X12h, r_X129) ->
           case Main.$w$sdigits q_X12h
           of _ [Occ=Dead] { (# ww1_s1pO, ww2_s1pP #) ->
           GHC.Types.: @ Int ww1_s1pO ww2_s1pP
           }
           } #);
      True ->
        (# GHC.Num.$fNumInt_$cfromInteger w_s1pI, GHC.Types.[] @ Int #)
    }
    }
end Rec }
实际上,我找不到
show.txt
的相关部分。我以后再做

马上,
digitsV2.hs
就会生成较短的代码。这可能是个好兆头

数字。hs
似乎遵循此伪代码:

def数字(w_s1pI):
如果w_s1pI<10:返回[fromInteger(w_s1pI)]
其他:
ds_s16Q=quotRem(w_s1pI,10)
q_X12h=ds_s16Q[0]
r_X12h=ds_s16Q[1]
wild3_a17c=整数点(r_X12h)
ww1_s1pO=r_X12h
ww2_s1pP=数字(q_X12h)
第二次世界大战第1次世界大战第1次世界大战第1次世界大战第1次世界大战第1次世界大战
返回ww2_s1pP
digitsV2.hs
似乎遵循以下伪代码:

def digitsV2(w_s1wh,w1_s1wi=[]):#实际上伪装成go(),如@No_signal所写
如果w_s1wh<10:
w1_s1wi.pushFront(从整数(w_s1wh))
返回w1_s1wi
其他:
ipv_a1dB,ipv1_a1dC=quotRem(w_s1wh,10)
w1_s1wi.推前(集成电路(ipv1a1dC))
返回数字V2(ipv1_a1dC,w1_s1wi)

这些函数可能不会像我的psuedocode所建议的那样改变列表,但这立即暗示了一些事情:看起来好像
digitsV2
是完全尾部递归的,而
digits
实际上不是(可能必须使用一些Haskell蹦床或其他东西)。似乎Haskell需要将所有余数存储在
数字
中,然后才能将它们全部推到列表的前面,而它只需将它们推到
数字v2
中,就可以将它们忘掉。这纯粹是推测,但它是有充分根据的推测。

您正在运行编译后的代码吗?您是否将解释的
数字
与调用将运行编译代码的
show
进行比较?@user5402我想我没有运行编译代码。现在尝试。尝试重新定义
数字::Int->[Int]
,如果这在问题范围内是合法的话。@kwartz可以加快速度,但速度不会太快,而且我还需要找到数字的数字,如
2^1000
100。在这种情况下,我认为
digitshow
在这里速度更快,原因无非是
show
几乎已经完成了您想要做的事情。Int-to-string转换是一个非常低级的操作(我认为某些处理器上的汇编程序)。出于好奇,您是否尝试过
digitsByShow=map read。显示
?我想它应该慢一点,但话说回来……这能让它快一点吗?为什么我的版本较慢?这和易变性有什么关系吗?爆炸模式对我来说几乎没有加快任何速度。@Justin它对我有效。你把它放在哪里了,
!(q,r)
数字!第二个没有用。我完全复制了你写的代码。它加快了速度,但没有您建议的那么快。您可以使用
-ddump siml
查看核心代码。现实世界的哈斯克尔在第25章谈到了这一点
ghc -O2 -ddump-simpl digits.hs >digits.txt
ghc -O2 -ddump-simpl digitsV2.hs >digitsV2.txt
ghc -O2 -ddump-simpl show.hs >show.txt
digits :: (Integral a) => a -> [Int]
digits x
    | x < 10 = [fromIntegral x]
    | otherwise = let (q, r) = x `quotRem` 10 in (fromIntegral r) : (digits q)

main = return $ digits 1
digitsV2 :: (Integral a) => a -> [Int]
digitsV2 n = go n []
    where
      go x xs
          | x < 10    = fromIntegral x : xs
          | otherwise = let (q, r) = x `quotRem` 10 in go q (fromIntegral r : xs)

main = return $ digits 1
import Data.Char

digitsByShow :: (Integral a, Show a) => a -> [Int]
digitsByShow = map (\x -> ord x - ord '0') . show

main = return $ digitsByShow 1
lvl_r1qU = __integer 10

Rec {
Main.$w$sdigits [InlPrag=[0], Occ=LoopBreaker]
  :: Integer -> (# Int, [Int] #)
[GblId, Arity=1, Str=DmdType <S,U>]
Main.$w$sdigits =
  \ (w_s1pI :: Integer) ->
    case integer-gmp-1.0.0.0:GHC.Integer.Type.ltInteger#
           w_s1pI lvl_r1qU
    of wild_a17q { __DEFAULT ->
    case GHC.Prim.tagToEnum# @ Bool wild_a17q of _ [Occ=Dead] {
      False ->
        let {
          ds_s16Q [Dmd=<L,U(U,U)>] :: (Integer, Integer)
          [LclId, Str=DmdType]
          ds_s16Q =
            case integer-gmp-1.0.0.0:GHC.Integer.Type.quotRemInteger
                   w_s1pI lvl_r1qU
            of _ [Occ=Dead] { (# ipv_a17D, ipv1_a17E #) ->
            (ipv_a17D, ipv1_a17E)
            } } in
        (# case ds_s16Q of _ [Occ=Dead] { (q_a11V, r_X12h) ->
           case integer-gmp-1.0.0.0:GHC.Integer.Type.integerToInt r_X12h
           of wild3_a17c { __DEFAULT ->
           GHC.Types.I# wild3_a17c
           }
           },
           case ds_s16Q of _ [Occ=Dead] { (q_X12h, r_X129) ->
           case Main.$w$sdigits q_X12h
           of _ [Occ=Dead] { (# ww1_s1pO, ww2_s1pP #) ->
           GHC.Types.: @ Int ww1_s1pO ww2_s1pP
           }
           } #);
      True ->
        (# GHC.Num.$fNumInt_$cfromInteger w_s1pI, GHC.Types.[] @ Int #)
    }
    }
end Rec }
lvl_r1xl = __integer 10

Rec {
Main.$wgo [InlPrag=[0], Occ=LoopBreaker]
  :: Integer -> [Int] -> (# Int, [Int] #)
[GblId, Arity=2, Str=DmdType <S,U><L,U>]
Main.$wgo =
  \ (w_s1wh :: Integer) (w1_s1wi :: [Int]) ->
    case integer-gmp-1.0.0.0:GHC.Integer.Type.ltInteger#
           w_s1wh lvl_r1xl
    of wild_a1dp { __DEFAULT ->
    case GHC.Prim.tagToEnum# @ Bool wild_a1dp of _ [Occ=Dead] {
      False ->
        case integer-gmp-1.0.0.0:GHC.Integer.Type.quotRemInteger
               w_s1wh lvl_r1xl
        of _ [Occ=Dead] { (# ipv_a1dB, ipv1_a1dC #) ->
        Main.$wgo
          ipv_a1dB
          (GHC.Types.:
             @ Int
             (case integer-gmp-1.0.0.0:GHC.Integer.Type.integerToInt ipv1_a1dC
              of wild2_a1ea { __DEFAULT ->
              GHC.Types.I# wild2_a1ea
              })
             w1_s1wi)
        };
      True -> (# GHC.Num.$fNumInt_$cfromInteger w_s1wh, w1_s1wi #)
    }
    }
end Rec }