我已在Project Euler's Challenge 14和Haskell(ideone链接)中为C++编写了代码。他们都记得以前在数组中做过的任何计算。
分别使用ghc -O2
和g++ -O3
,C ++运行速度比Haskell版本快10-15倍。
虽然我理解Haskell版本可能运行速度较慢,并且Haskell是一种更好的语言,但我很高兴知道我可以对Haskell版本进行一些代码更改以使其运行得更快(理想情况是在一个因素内) 2或3的C ++版本?)
Haskell代码在这里:
import Data.Array
import Data.Word
import Data.List
collatz_array =
let
upperbound = 1000000
a = array (1, upperbound) [(i :: Word64, f i :: Int) | i <- [1..upperbound]]
f i = i `seq`
let
check_f i = i `seq` if i <= upperbound then a ! i else f i
in
if (i == 1) then 0 else (check_f ((if (even i) then i else 3 * i + 1) `div` 2)) + 1
in a
main =
putStrLn $ show $
foldl1' (\(x1,x2) (y1,y2) -> if (x2 >= y2) then (x1, x2) else (y1, y2)) $! (assocs collatz_array)
修改
我现在还使用未装箱的可变数组完成了一个版本。它仍然比C ++版本慢5倍,但是有了显着的改进。代码位于ideone here。
我想知道对可变阵列版本的改进,使其更接近C ++版本。
答案 0 :(得分:4)
您的(可变数组)代码存在一些问题:
even
和div
来测试resp除以2.这些都很慢。 g ++优化了两个操作到更快的位操作(至少在更快的平台上),但GHC不进行这些低级优化(目前),所以目前它们必须手工完成readArray
和writeArray
。在处理其他问题时,在C ++代码中未执行的额外边界检查也需要时间,这相当于运行时间的很大一部分(我的盒子上大约25%),因为已经完成算法中有很多读写操作。将其纳入实施,我得到了
import Data.Array.ST
import Data.Array.Base
import Control.Monad.ST
import Data.Bits
collatz_array :: ST s (STUArray s Int Int)
collatz_array = do
let upper = 10000000
arr <- newArray (0,upper) 0
unsafeWrite arr 2 1
let check i
| upper < i = return arr
| i .&. 1 == 0 = do
l <- unsafeRead arr (i `shiftR` 1)
unsafeWrite arr i (l+1)
check (i+1)
| otherwise = do
let j = (3*i+1) `shiftR` 1
find k l
| upper < k = find (next k) $! l+1
| k < i = do
m <- unsafeRead arr k
return (m+l)
| otherwise = do
m <- unsafeRead arr k
if m == 0
then do
n <- find (next k) 1
unsafeWrite arr k n
return (n+l)
else return (m+l)
where
next h
| h .&. 1 == 0 = h `shiftR` 1
| otherwise = (3*h+1) `shiftR` 1
l <- find j 1
unsafeWrite arr i l
check (i+1)
check 3
collatz_max :: ST s (Int,Int)
collatz_max = do
car <- collatz_array
(_,upper) <- getBounds car
let find w m i
| upper < i = return (w,m)
| otherwise = do
l <- unsafeRead car i
if m < l
then find i l (i+1)
else find w m (i+1)
find 1 0 2
main :: IO ()
main = print (runST collatz_max)
和时间(均为1000万):
$ time ./cccoll
8400511 429
real 0m0.210s
user 0m0.200s
sys 0m0.009s
$ time ./stcoll
(8400511,429)
real 0m0.341s
user 0m0.307s
sys 0m0.033s
看起来不太糟糕。
重要说明:该代码仅适用于64位GHC(因此,特别是在Windows上,您需要ghc-7.6.1或更高版本,之前的GHC甚至在64位上也是32位-bit Windows)因为中间链元素超过32位范围。在32位系统上,由于原语,因此必须使用Integer
或64位整数类型(Int64
或Word64
)来跟踪链,从而导致性能成本极高64位操作(算术和移位)实现为32位GHC中的C函数的外部调用(快速外部调用,但仍然比直接机器操作慢得多)。
答案 1 :(得分:2)
ideone网站正在使用ghc 6.8.2,这已经很老了。在ghc版本7.4.1上,差异要小得多。
用ghc:
$ ghc -O2 euler14.hs && time ./euler14
(837799,329)
./euler14 0.63s user 0.04s system 98% cpu 0.685 total
使用g ++ 4.7.0:
$ g++ --std=c++0x -O3 euler14.cpp && time ./a.out
8400511 429
./a.out 0.24s user 0.01s system 99% cpu 0.252 total
对我来说,ghc版本只比c ++版本慢2.7倍。 此外,这两个程序没有给出相同的结果......(不是一个好的迹象,特别是对于基准测试)