目前我正在追赶Haskell,到目前为止我印象非常深刻。作为一个超级简单的测试,我写了一个程序,计算总和直到十亿。为了避免列表创建,我编写了一个应该是尾递归的函数
summation start upto
| upto == 0 = start
| otherwise = summation (start+upto) (upto-1)
main = print $ summation 0 1000000000
用-O2运行这个我在我的机器上得到大约20秒的运行时间,这让我感到很惊讶,因为我认为编译器会更优化。作为比较,我写了一个简单的c ++程序
#include <iostream>
int main(int argc, char *argv[]) {
long long result = 0;
int upto = 1000000000;
for (int i = 0; i < upto; i++) {
result += i;
}
std::cout << result << std::end;
return 0;
}
使用clang ++进行编译而不进行优化,运行时间为〜3秒。所以我想知道为什么我的Haskell解决方案如此之慢。有人有想法吗?
在OSX上:
clang ++ --version:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.2.0
Thread model: posix
ghc --version:
The Glorious Glasgow Haskell Compilation System, version 7.10.3
答案 0 :(得分:34)
添加类型签名使我的运行时间从14.35秒降至0.27。它现在比我的机器上的C ++更快。当性能很重要时,不要依赖于类型默认。例如,对于在Web应用程序中对域进行建模,Ints并不是优选的,但如果您想要一个紧密的循环,它们就会很好。
module Main where
summation :: Int -> Int -> Int
summation start upto
| upto == 0 = start
| otherwise = summation (start+upto) (upto-1)
main = print $ summation 0 1000000000
[1 of 1] Compiling Main ( code/summation.hs, code/summation.o )
Linking bin/build ...
500000000500000000
14.35user 0.06system 0:14.41elapsed 100%CPU (0avgtext+0avgdata 3992maxresident)k
0inputs+0outputs (0major+300minor)pagefaults 0swaps
Linking bin/build ...
500000000500000000
0.27user 0.00system 0:00.28elapsed 98%CPU (0avgtext+0avgdata 3428maxresident)k
0inputs+0outputs (0major+171minor)pagefaults 0swaps
答案 1 :(得分:6)
除非您想要查看未经优化的(非-O2)视图,否则请跳过删除线。
让我们看看评估:
summation start upto
| upto == 0 = start
| otherwise = summation (start+upto) (upto-1)
main = print $ summation 0 1000000000
- &GT;
summation 0 1000000000
- &GT;
summations (0 + 1000000000) 999999999
- &GT;
summation (0 + 1000000000 + 999999999) 999999998
- &GT;
summation (0 + 1000000000 + 999999999 + 999999998) 999999997
击> <击> 撞击>
编辑:我没有看到您使用-O2
进行编译,因此上述情况并未发生。即使没有任何严格的注释,累加器也可以在大多数情况下使用适当的优化级别。
<罢工>哦不!你将10亿个数字存储在一个你还没有评估过的大数据中! TISK!有许多使用累加器和严格性的解决方案 - 似乎大多数stackoverflow答案与此问题附近的任何内容都足以教你除了fold{l,r}
之外的库函数,它们可以帮助你避免编写自己的原始递归函数。既然你可以环顾四周和/或询问这些概念,我会用这个答案切入追逐。
如果您真的想以正确的方式执行此操作,那么您可以使用列表并了解Haskell编译器可以执行&#34;砍伐森林&#34;这意味着亿元素列表从未实际分配过:
main = print (sum [0..1000000000])
然后:
% ghc -O2 x.hs
[1 of 1] Compiling Main ( x.hs, x.o )
Linking x ...
% time ./x
500000000500000000
./x 16.09s user 0.13s system 99% cpu 16.267 total
很酷,但为什么16秒?那么默认情况下,这些值是整数(GHC编译器的GMP整数),并且比机器Int
慢。让我们使用Int
!
% cat x.hs
main = print (sum [0..1000000000] :: Int)
tommd@HalfAndHalf /tmp% ghc -O2 x.hs && time ./x
500000000500000000
./x 0.31s user 0.00s system 99% cpu 0.311 total