Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 哈斯克尔:为什么Int的性能比Word64差,为什么我的程序比C慢得多?_Performance_Haskell_Optimization - Fatal编程技术网

Performance 哈斯克尔:为什么Int的性能比Word64差,为什么我的程序比C慢得多?

Performance 哈斯克尔:为什么Int的性能比Word64差,为什么我的程序比C慢得多?,performance,haskell,optimization,Performance,Haskell,Optimization,我读过的一篇文章,基本上是这样写的,如果你一直乘以三,然后把一加到一个奇数,或者把一个偶数除以二,你最终会得到一。例如,3->10->5->16->8->4->2->1 本文给出的程序是计算给定范围内的最长Collatz序列。C版本是: #include <stdio.h> int main(int argc, char **argv) { int max_a0 = atoi(argv[1]); int longest = 0, max_len = 0; int

我读过的一篇文章,基本上是这样写的,如果你一直乘以三,然后把一加到一个奇数,或者把一个偶数除以二,你最终会得到一。例如,3->10->5->16->8->4->2->1

本文给出的程序是计算给定范围内的最长Collatz序列。C版本是:

#include <stdio.h>

int main(int argc, char **argv) {
   int max_a0 = atoi(argv[1]);
   int longest = 0, max_len = 0;
   int a0, len;
   unsigned long a;

   for (a0 = 1; a0 <= max_a0; a0++) {
      a = a0;
      len = 0;

      while (a != 1) {
         len++;
         a = ((a%2==0)? a : 3*a+1)/2;
      }

      if (len > max_len) {
         max_len = len;
         longest = a0;
      }
   }
   printf("(%d, %d)\n", max_len, longest);
   return 0;
}
#包括
int main(int argc,字符**argv){
int max_a0=atoi(argv[1]);
int longest=0,max_len=0;
int a0,len;
无符号长a;
对于(a0=1;a0最大长度){
max_len=len;
最长=a0;
}
}
printf((%d,%d)\n),最大长度,最长长度;
返回0;
}
用ClangO2编译,它在我的电脑上运行了0.2秒

文章中给出的Haskell版本将整个序列显式生成为一个列表,然后计算中间列表的长度。它比C版本慢10倍。然而,由于作者使用LLVM作为后端,我还没有安装它,所以我无法复制它。使用GHC 7.8和默认后端,它在我的Mac上运行10秒,比C版本慢50倍

然后,我使用尾部递归编写了一个版本,但没有生成中间列表:

collatzNext :: Int -> Int
collatzNext a
  | even a    = a `div` 2
  | otherwise = (3 * a + 1) `div` 2

collatzLen :: Int -> Int
collatzLen n = collatzIter n 0
  where
    collatzIter 1 len = len
    collatzIter n len = collatzIter (collatzNext n) (len + 1)

main = do
  print $ maximum $ [collatzLen x | x <- [1..1000000]]
collatzNext::Int->Int
羽衣甘蓝
|偶数a=a`div`2
|否则=(3*a+1)`div`2
衣领::Int->Int
collatzLen n=collatzIter n 0
哪里
科拉兹特1透镜=透镜
collatzIter n len=collatzIter(collatzIter n)(len+1)
main=do

print$maximum$[collatzLen x | x此程序的性能取决于几个因素。如果我们都正确,则性能与C程序的性能相同。通过这些因素:

1.使用和比较正确的字号

发布的C代码片段并不完全正确;它在所有体系结构上都使用32位整数,而Haskell
Int
-s在64位机器上是64位的。在做其他事情之前,我们应该确保在两个程序中使用相同的字长

此外,我们应该在Haskell代码中始终使用本机大小的整数类型。因此,如果我们在64位系统上,我们应该使用64位数字,并且避免使用
Int32
-s和
Word32
-s,除非有特殊需要。这是因为对非本机整数的操作主要是作为实现的,因此它们是有意义的太慢了

2.在
collatzNext

div
对于
Int
quot慢,因为
div
处理负数。如果我们使用
div
并切换到
Word
,程序会变得更快,因为
div
Word
quot
Int
一样工作ne。然而,这仍然不如C快。我们可以通过右移位将其除以2。由于某些原因,在本例中,即使LLVM也不会进行这种特殊的强度降低,因此我们最好手动进行,将
quot n2
替换为
shiftR n1

3.检查平整度

检查这一点的最快方法是检查最低有效位。LLVM可以将
偶数
优化到这一点,而本机codegen不能。因此,如果我们使用本机codegen,
偶数n
可以替换为
n.&.1==0
,这将大大提高性能

然而,我在GHC 7.10中发现了一些性能缺陷。在这里,我们没有为
Word
提供内联
甚至
,这会破坏性能(在代码最热的部分调用具有堆分配
Word
框的函数可以做到这一点)因此,这里我们应该使用
remn2==0
n.&1==0
而不是
偶数
。尽管
Int
偶数可以很好地内联

4.在
collatzLen

这是一个关键因素。与此相关的链接博客文章有点过时。GHC 7.8不能在这里进行融合,但7.10可以。这意味着使用GHC 7.10和LLVM,我们可以方便地获得类似于C的性能,而无需显著修改原始代码

collatzNext a = (if even a then a else 3*a+1) `quot` 2
collatzLen a0 = length $ takeWhile (/= 1) $ iterate collatzNext a0
maxColLen n   = maximum $ map collatzLen [1..n]

main = do
    [n] <- getArgs
    print $ maxColLen (read n :: Int) 
现在,对于
ghc-7.10.1-O2-fllvm
n=10000000
,上面的代码在2.1秒内运行,而C程序在2.4秒内运行。如果我们想在没有LLVM和ghc 7.10的情况下获得类似的性能,我们只需手动应用重要的缺失优化:

collatzLen :: Int -> Int
collatzLen = go 0 where
  go l 1 = l
  go l n | n .&. 1 == 0 = go (l + 1) (shiftR n 1)
         | otherwise    = go (l + 1) (shiftR (3 * n + 1) 1)

maxCol :: Int -> Int
maxCol = go 1 1 where
  go ml i n | i > n = ml
  go ml i n = go (max ml (collatzLen i)) (i + 1) n

main = do
  [n] <- getArgs
  print $ maxCol (read n :: Int)
collatzLen::Int->Int
collatzLen=go 0,其中
走L1=l
走l n | n.和.1==0=走(l+1)(移位1)
|否则=前进(l+1)(移位(3*n+1)1)
maxCol::Int->Int
maxCol=转到1,其中
去ml i n | i>n=ml
go ml i n=go(最大ml(衣领i))(i+1)n
main=do

[n]只是一个注释,不适合比较任何低于1秒的运行时间。可能库加载需要时间?谁知道呢。尝试对代码的特定部分计时,并使用更大的问题或多次计算。@VladimirF OS开销可以大致测量:通过将n从1000000减少到100,此程序使用的时间是
/a0.00s用户0.00s系统29%cpu 0.013总计
,当n=1000000时,时间为
/a 1.14s用户0.01s系统99%cpu 1.157总计
。我还检查了RTS的GC报告,GC中使用的时间小于1%。您应该注意,
2
幂次除法运算通常对于无符号整数比有符号整数更快。而且这可能就是为什么你的
Word
实例更快的原因,因为
Word
代表无符号整数。Int的
div
函数比Word慢。为了提高速度,你应该将
quot
与Int.BTW一起使用,
collatzNext
函数在这里和链接的博客文章中是错误的。
collatzLen :: Int -> Int
collatzLen = go 0 where
  go l 1 = l
  go l n | n .&. 1 == 0 = go (l + 1) (shiftR n 1)
         | otherwise    = go (l + 1) (shiftR (3 * n + 1) 1)

maxCol :: Int -> Int
maxCol = go 1 1 where
  go ml i n | i > n = ml
  go ml i n = go (max ml (collatzLen i)) (i + 1) n

main = do
  [n] <- getArgs
  print $ maxCol (read n :: Int)