C vs Haskell Collatz conjecture speed comparison
我第一次真正的编程经验是在Haskell。为了满足我的特殊需要,我需要一个易于学习、易于编码和易于维护的工具,我可以说它很好地完成了任务。
然而,在某一时刻,我的任务规模变得更大了,我认为C可能更适合他们,它确实做到了。也许我在编程方面不够熟练,但我没能使haskell像c一样快速高效,尽管我听说适当的haskell有类似的性能。
最近,我想我会再次尝试一些haskell,虽然它对于一般的简单(在计算方面)任务仍然很好,但它似乎无法将C的速度与collatz猜想等问题匹配起来。我读过:
与项目euler的速度比较:c与python与erlang与haskell
GHC优化:collatz猜想
使用haskell的collatz列表实现
但从我看来,简单的优化方法包括:
- 选择"更紧"的类型,如int64而不是integer
- 打开GHC优化
- 使用简单的优化技术,如避免不必要的计算或更简单的函数
对于真正的大数字来说,仍然不能使haskell代码接近几乎相同的(在方法论方面)C代码。唯一能使它的性能与C的(对于大规模问题)相媲美的是使用优化方法,使代码成为一个长的、可怕的单态地狱,这违背了haskell(和i)非常重视的原则。
这是C版:
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 | #include <stdio.h> #include <stdlib.h> #include <stdint.h> int32_t col(int64_t n); int main(int argc, char **argv) { int64_t n = atoi(argv[1]), i; int32_t s, max; for(i = 2, max = 0; i <= n; ++i) { s = col(i); if(s > max) max = s; } printf("%d ", max); return 0; } int32_t col(int64_t n) { int32_t s; for(s = 0; ; ++s) { if(n == 1) break; n = n % 2 ? 3 * n + 1 : n / 2; } return s; } |
哈斯克尔版本:
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 | module Main where import System.Environment (getArgs) import Data.Int (Int32, Int64) main :: IO () main = do arg <- getArgs print $ maxCol 0 (read (head arg) :: Int64) col :: Int64 -> Int32 col x = col' x 0 col' :: Int64 -> Int32 -> Int32 col' 1 n = n col' x n | rem x 2 == 0 = col' (quot x 2) (n + 1) | otherwise = col' (3 * x + 1) (n + 1) maxCol :: Int32 -> Int64 -> Int32 maxCol maxS 2 = maxS maxCol maxS n | s > maxS = maxCol s (n - 1) | otherwise = maxCol maxS (n - 1) where s = col n |
tl;dr:haskell代码的编写速度快,维护简单,仅用于计算简单的任务,在性能至关重要时会失去这一特性吗?
haskell代码的最大问题是您正在划分,而在C版本中您没有划分。
是的,您编写了
如果你自己做替换
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 | module Main where import System.Environment (getArgs) import Data.Int (Int32, Int64) import Data.Bits main :: IO () main = do arg <- getArgs print $ maxCol 0 (read (head arg) :: Int64) col :: Int64 -> Int32 col x = col' x 0 col' :: Int64 -> Int32 -> Int32 col' 1 n = n col' x n | x .&. 1 == 0 = col' (x `shiftR` 1) (n + 1) | otherwise = col' (3 * x + 1) (n + 1) maxCol :: Int32 -> Int64 -> Int32 maxCol maxS 2 = maxS maxCol maxS n | s > maxS = maxCol s (n - 1) | otherwise = maxCol maxS (n - 1) where s = col n |
使用64位GHC,您可以获得类似的速度(0.35s与C的0.32s在我的盒子上,限制为1000000)。如果您使用llvm后端进行编译,甚至不需要用逐位操作替换
有了32位GHC,您将无法获得可比的速度,因为有了这些,64位整数上的原始操作是通过C调用实现的——对于32位系统上的64位快速操作,没有足够的需求让它们作为primops实现;在GHC上工作的少数人花费时间做其他更重要的事情。
TL;DR: Is Haskell code quick to write and simple to maintain only for computationally simple tasks and loses this characteristic when performance is crucial?
那要视情况而定。您必须了解GHC从什么类型的输入中生成什么类型的代码,并且必须避免一些性能陷阱。经过一点实践,很容易达到gcc-o3的2倍速度。