GHC Optimization: Collatz conjecture
我已经为项目Euler的挑战14编写了代码,在Haskell和C++中(IDENEN链接)。它们都会记住以前在数组中所做的任何计算。
使用EDCOX1×0和EDCOX1,1的方法,C++的速度比Haskell版本快10-15倍。
虽然我理解Haskell版本可能运行得比较慢,而Haskell是一种更好的编写语言,但我知道我可以对Haskell版本做一些代码修改,以使它运行得更快(理想情况下是C++版本的2或3的一个因素)。
Haskell代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) |
编辑:
我现在还使用了一个不固定可变数组的版本。它仍然是5倍慢于C++版本,但有显著的改进。代码在这里的IDeone上。
我想知道对可变数组版本的改进,使它更接近C++版本。
您的(可变数组)代码有一些问题:
- 使用折叠查找最大链长,因为数组必须转换为关联列表,这需要时间和分配C++版本不需要。
- 您使用
even 和div 来测试resp除以2。这些都很慢。G++将两种操作都优化为更快的位操作(至少在假定速度更快的平台上),但GHC还没有进行这些低级优化(然而),因此目前,它们必须手工完成。 - 使用
readArray 和writeArray 。在C++代码中没有做的额外边界检查也需要时间,一旦处理了其他问题,这就相当于运行时间的一个重要部分(在我的框中占25%),因为在算法中已经做了大量的读写。
把它合并到实现中,我得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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万):
1 2 3 4 5 6 7 8 9 10 11 12 | $ 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位Windows上也是32位的),因为中间链元素超过32位范围。在32位系统上,由于原始的64位操作(算术和移位)是在32位GHC中作为对C函数的外部调用(快速的外部调用,但仍然比直接调用慢得多),因此必须使用
Ideone网站正在使用一个已经很老了的GHC 6.8.2。在GHC 7.4.1版上,差异要小得多。
使用GHC:
1 2 3 | $ ghc -O2 euler14.hs && time ./euler14 (837799,329) ./euler14 0.63s user 0.04s system 98% cpu 0.685 total |
使用G++4.7.0:
1 2 3 | $ 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倍。另外,这两个程序的结果不一样…(不是很好的迹象,尤其是基准测试)