Performance 为什么是";“更好”;数字列表功能更慢?
我在玩ProjectEuler#34,我写了以下函数: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
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 }