考虑这个简单的“基准”:
n :: Int
n = 1000
main = do
print $ length [(a,b,c) | a<-[1..n],b<-[1..n],c<-[1..n],a^2+b^2==c^2]
和适当的C
版本:
#include <stdio.h>
int main(void)
{
int a,b,c, N=1000;
int cnt = 0;
for (a=1;a<=N;a++)
for (b=1;b<=N;b++)
for (c=1;c<=N;c++)
if (a*a+b*b==c*c) cnt++;
printf("%d\n", cnt);
}
汇编:
ghc -O2 triangle.hs
(ghc 7.4.1)gcc -O2 -o triangle-c triangle.c
(gcc 4.6.3)运行时间:
即使对于如此简单且可以很好地优化的程序,Haskell几乎慢了4倍,它是否行为正常? Haskell在哪里浪费时间?
答案 0 :(得分:8)
Haskell版本浪费时间分配盒装整数和元组。
您可以通过运行带有标志+RTS -s
的haskell程序来验证这一点。对我来说,输出的统计数据包括:
80,371,600 bytes allocated in the heap
C版本的直接编码更快,因为编译器可以使用未装箱的整数并跳过分配元组:
n :: Int
n = 1000
main = do
print $ f n
f :: Int -> Int
f max = go 0 1 1 1
where go cnt a b c
| a > max = cnt
| b > max = go cnt (a+1) 1 1
| c > max = go cnt a (b+1) 1
| a^2+b^2==c^2 = go (cnt+1) a b (c+1)
| otherwise = go cnt a b (c+1)
请参阅:
51,728 bytes allocated in the heap
对于C版本,此版本的运行时间为1.920s
与1.212s
。
答案 1 :(得分:4)
我不知道你的“替补”有多大关系。 我同意list-comprehension语法是“很好”使用,但如果你想比较两种语言的性能,你应该在更公平的测试中比较它们。 我的意思是,创建一个可能包含很多元素的列表然后计算它的长度就像在(三重循环)中递增计数器一样。
所以也许haskell有一些很好的优化可以检测你在做什么并且永远不会创建列表,但是我不会依赖于那个代码,你可能也不应该。
如果你需要快速计算,我认为你不会像这样编写你的程序代码,为什么要为这个工作台做这个呢?
答案 2 :(得分:3)
Haskell 可以进行优化 - 但是你需要适当的技术,你需要知道你在做什么。
这个列表理解语法优雅而浪费。您应该阅读appropriate chapter of RealWorldHaskell以了解有关您的分析机会的更多信息。在这个确切的情况下,你创建了许多列表刺和盒装Int
,完全没有任何理由。请参见此处:
你一定要对此采取行动。编辑:@opqdonut刚刚发布了一个很好的答案,如何加快速度。
请记住下次在比较任何基准测试之前分析您的应用程序。 Haskell使编写惯用代码变得容易,但它也隐藏了很多复杂性。