Performance 哈斯克尔:为什么Int的性能比Word64差,为什么我的程序比C慢得多?
我读过的一篇文章,基本上是这样写的,如果你一直乘以三,然后把一加到一个奇数,或者把一个偶数除以二,你最终会得到一。例如,3->10->5->16->8->4->2->1 本文给出的程序是计算给定范围内的最长Collatz序列。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
#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位整数,而HaskellInt
-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)