将所有数字从Haskell中的1到10亿加起来

时间:2016-01-26 17:23:08

标签: performance haskell

目前我正在追赶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

2 个答案:

答案 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