素数的计算比较哈斯克尔和C的速度(Comparing speed of Haskell and C

2019-09-21 08:24发布

我最初写这个(蛮力和低效)与确保的意图计算素数的方法,有使用“IF-THEN-ELSE”与Haskell中后卫(并没有什么区别!)之间的速度没有区别。 但后来我决定写一个C程序来比较和我得到了以下(Haskell的速度慢一些刚刚超过25%):

(请注意我用的REM,而不是国防部的观点,并从编译器调用的O3选项下面的帖子: 在与C在斐波那契微基准提高Haskell的性能 )

哈斯克尔:Forum.hs

divisibleRec :: Int -> Int -> Bool
divisibleRec i j 
  | j == 1         = False 
  | i `rem` j == 0 = True 
  | otherwise      = divisibleRec i (j-1)

divisible::Int -> Bool
divisible i = divisibleRec i (i-1)

r = [ x | x <- [2..200000], divisible x == False]

main :: IO()
main = print(length(r))

C:main.cpp中

#include <stdio.h>

bool divisibleRec(int i, int j){
  if(j==1){ return false; }
  else if(i%j==0){ return true; }
  else{ return divisibleRec(i,j-1); }
}

bool divisible(int i){ return divisibleRec(i, i-1); }

int main(void){
  int i, count =0;
  for(i=2; i<200000; ++i){
    if(divisible(i)==false){
      count = count+1;
    }
  }
  printf("number of primes = %d\n",count);
  return 0;
}

我得到的结果如下:

编译时间

time (ghc -O3 -o runProg Forum.hs)
real    0m0.355s
user    0m0.252s
sys 0m0.040s

time (gcc -O3 -o runProg main.cpp)
real    0m0.070s
user    0m0.036s
sys 0m0.008s

和下面的运行时间:

在Ubuntu 32位运行时间

Haskell
17984
real    0m54.498s
user    0m51.363s
sys 0m0.140s


C++
number of primes = 17984
real    0m41.739s
user    0m39.642s
sys 0m0.080s

我是哈斯克尔的运行时间相当深刻的印象。 但是我的问题是这样的:我可以做任何事情,以加快Haskell的程序而不:

  1. 改变底层的算法(很显然,大规模的加速可以通过改变算法来获得,但我只是想了解什么,我可以在语言/编译器侧做,以提高性能)
  2. 调用LLVM编译器(因为我没有这个安装)

[编辑:内存使用]

阿伦评论后,我注意到,在C程序使用的内存恒定的量,其中作为Haskell的计划增长缓慢的内存大小。 起初我以为这有一些东西需要用递归,但GSPR下面解释为什么发生这种情况,并提供了解决方案。 将尼斯提供了一种替代的解决方案,其中(像GSPR的溶液)也确保了存储器保持静态。

[编辑:更大的运行概要]

最大号测试:200000:

(54.498s / 41.739s)= Haskell的慢30.5%

最大号测试40万:

3m31.372s / 2m45.076s = 211.37s / 165S = Haskell的慢28.1%

最大号测试80万:

14m3.266s / 11m6.024s = 843.27s / 666.02s = Haskell的慢26.6%

[编辑:代码艾伦]

这是我此前不具备递归和我20万测试编写的代码:

#include <stdio.h>

bool divisibleRec(int i, int j){
  while(j>0){
    if(j==1){ return false; }
    else if(i%j==0){ return true; }
    else{ j -= 1;}
  }
}


bool divisible(int i){ return divisibleRec(i, i-1); }

int main(void){
  int i, count =0;
  for(i=2; i<8000000; ++i){
    if(divisible(i)==false){
      count = count+1;
    }
  }
  printf("number of primes = %d\n",count);
  return 0;
}

对于具有和不具有递归C代码的结果如下(为80万):

递归:11m6.024s

如果没有递归:11m5.328s

注意,可执行似乎占用60KB(如系统监视看到的),而不管最大数量的,因此我怀疑是编译器检测该递归。

Answer 1:

这是不是真的回答你的问题,而是你在成长方面的内存使用情况,当数20万增长注释问道。

当该号码的增加,有列表r 你的代码需要所有的r在最后,计算它的长度。 C代码,而另一方面,只是增加一个计数器。 你必须,如果你想不断内存使用情况做在Haskell类似的东西太多。 该代码将仍然是非常Haskelly,一般这是一个明智的命题:你并不真正需要哪些号码列表divisibleFalse ,你只需要知道有多少。

你可以试试

main :: IO ()
main = print $ foldl' (\s x -> if divisible x then s else s+1) 0 [2..200000]

foldl'是严格foldlData.List避免的thunk正在建立)。



Answer 2:

嗯一声patters给你一个非常小赢(一样LLVM,但你似乎已经预计):

{-# LANUGAGE BangPatterns #-}

divisibleRec !i !j | j == 1         = False

而在我的x86-64我通过切换到更小的表示,如Word32得到一个非常大的胜利:

divisibleRec :: Word32 -> Word32 -> Bool
...
divisible :: Word32 -> Bool

我的计时:

$ time ./so             -- Int
2262

real    0m2.332s

$ time ./so              -- Word32
2262

real    0m1.424s

这是一个更匹配你的C程序,它仅使用int 。 它仍然不匹配睿智的表现,我想我们不得不看核心找出原因。

编辑:和内存使用,因为已经提到我看到的,是关于命名列表r 我只是内联r使其输出1为每个非divisble值并把总和:

main = print $ sum $ [ 1 | x <- [2..800000], not (divisible x) ]


Answer 3:

你的算法写下另一种方式是

main = print $ length [()|x<-[2..200000], and [rem x d>0|d<-[x-1,x-2..2]]]

不幸的是,它运行速度较慢。 使用all ((>0).rem x) [x-1,x-2..2]作为测试,它运行较慢仍然。 但是,也许你还是测试你的设置。

与外在的循环与爆炸图案制作没有任何区别更换你的代码:

{-# OPTIONS_GHC -XBangPatterns #-}
r4::Int->Int
r4 n = go 0 2 where
  go !c i | i>n = c
          | True = go (if not(divisible i) then (c+1) else c) (i+1)

divisibleRec::Int->Int->Bool
divisibleRec i !j | j == 1 = False 
                  | i `rem` j == 0 = True 
                  | otherwise = divisibleRec i (j-1)


Answer 4:

当我在Haskell开始编程我也印象深刻了解它的速度。 你可能有兴趣在阅读点5“哈斯克尔的速度”这个的文章 。



文章来源: Comparing speed of Haskell and C for the computation of primes