Haskell slower than Python in na?ve integer factorization?
我正在上数学课,我们必须做一些整数因式分解,作为解决问题的中间步骤。我决定编写一个python程序来为我做这件事(我们没有被测试我们的因素能力,所以这是完全超越董事会)。程序如下:
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 | #!/usr/bin/env python3 import math import sys # Return a list representing the prime factorization of n. The factorization is # found using trial division (highly inefficient). def factorize(n): def factorize_helper(n, min_poss_factor): if n <= 1: return [] prime_factors = [] smallest_prime_factor = -1 for i in range(min_poss_factor, math.ceil(math.sqrt(n)) + 1): if n % i == 0: smallest_prime_factor = i break if smallest_prime_factor != -1: return [smallest_prime_factor] \ + factorize_helper(n // smallest_prime_factor, smallest_prime_factor) else: return [n] if n < 0: print("Usage:" + sys.argv[0] +" n # where n >= 0") return [] elif n == 0 or n == 1: return [n] else: return factorize_helper(n, 2) if __name__ =="__main__": factorization = factorize(int(sys.argv[1])) if len(factorization) > 0: print(factorization) |
我也在教自己一些haskell,所以我决定尝试在haskell中重写程序。程序如下:
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 | import System.Environment -- Return a list containing all factors of n at least x. factorize' :: (Integral a) => a -> a -> [a] factorize' n x = smallestFactor : (if smallestFactor == n then [] else factorize' (n `quot` smallestFactor) smallestFactor) where smallestFactor = getSmallestFactor n x getSmallestFactor :: (Integral a) => a -> a -> a getSmallestFactor n x | n `rem` x == 0 = x | x > (ceiling . sqrt . fromIntegral $ n) = n | otherwise = getSmallestFactor n (x+1) -- Return a list representing the prime factorization of n. factorize :: (Integral a) => a -> [a] factorize n = factorize' n 2 main = do argv <- getArgs let n = read (argv !! 0) :: Int let factorization = factorize n putStrLn $ show (factorization) return () |
(注意:这需要64位环境。在32位上,导入
在我写下这篇文章之后,我决定比较这两个程序的性能,认识到有更好的算法,但是这两个程序使用的基本上是相同的算法。例如,我执行以下操作:
1 2 3 4 | $ ghc --make -O2 factorize.hs $ /usr/bin/time -f"%Uu %Ss %E" ./factorize 89273487253497 [3,723721,41117819] 0.18u 0.00s 0:00.23 |
然后,计时python程序:
1 2 3 | $ /usr/bin/time -f"%Uu %Ss %E" ./factorize.py 89273487253497 [3, 723721, 41117819] 0.09u 0.00s 0:00.09 |
当然,每次我运行一个程序时,时间都会略有不同,但它们总是在这个范围内,因为Python程序比编译好的haskell程序快几倍。在我看来,haskell版本应该能够运行得更快,我希望你能给我一个如何改进它的想法,这样就可以了。
我已经看到了一些优化haskell程序的提示,就像这个问题的答案一样,但似乎不能让我的程序运行得更快。循环比递归快得多吗?Haskell的I/O是否特别慢?我在实际执行算法时犯了错误吗?理想情况下,我希望有一个Haskell的优化版本,它仍然相对容易阅读。
如果只计算一次
1 2 3 4 5 6 7 | limit = ceiling . sqrt . fromIntegral $ n smallestFactor = getSmallestFactor x getSmallestFactor x | n `rem` x == 0 = x | x > limit = n | otherwise = getSmallestFactor (x+1) |
使用这个版本,我看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ time ./factorizePy.py 89273487253497 [3, 723721, 41117819] real 0m0.236s user 0m0.171s sys 0m0.062s $ time ./factorizeHs 89273487253497 [3,723721,41117819] real 0m0.190s user 0m0.000s sys 0m0.031s |
除了仙人掌的关键点之外,这里还有一些重构和严格注释的空间,以避免创建不必要的thunk。特别要注意,
1 | factorize' undefined undefined = undefined : undefined |
这并不是真正必要的,它迫使GHC分配几个thunk。其他地方的额外懒惰也是如此。我希望你能有更好的表现,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {-# LANGUAGE BangPatterns #-} factorize' :: Integral a => a -> a -> [a] factorize' n x | smallestFactor == n = [smallestFactor] | otherwise = smallestFactor : factorize' (n `quot` smallestFactor) smallestFactor where smallestFactor = getSmallestFactor n (ceiling . sqrt . fromIntegral $ n) x getSmallestFactor n !limit x | n `rem` x == 0 = x | x > limit = n | otherwise = getSmallestFactor n limit (x+1) -- Return a list representing the prime factorization of n. factorize :: Integral a => a -> [a] factorize n = factorize' n 2 |
我让