Подтвердить что ты не робот

Сумма всех чисел от одного до миллиарда в Haskell

В настоящее время я догоняю Haskell, и я до сих пор впечатлен. В качестве супер простого теста я написал программу, которая вычисляет сумму до миллиарда. Чтобы избежать создания списка, я написал функцию, которая должна быть хвостовой рекурсивной

summation start upto 
  | upto == 0 = start
  | otherwise = summation (start+upto) (upto-1)

main = print $ summation 0 1000000000

запустив это с -O2, я получаю на моей машине время около ~ 20 секунд, что меня удивило, так как я думал, что компилятор будет более оптимизирован. Для сравнения я написал простую программу на С++

#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
4b9b3361

Ответ 1

Добавление подписи типа сократило мое время выполнения с 14.35 секунд до 0.27. Теперь он быстрее, чем С++ на моей машине. Не следует полагаться на дефолт по типу, когда имеет значение производительность. 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

Ответ 2

Пропустите выпад, если вы не хотите увидеть неоптимизированный (не-O2) вид.

Давайте посмотрим на оценку:

summation start upto 
  | upto == 0 = start
  | otherwise = summation (start+upto) (upto-1)

main = print $ summation 0 1000000000

- >

summation 0 1000000000

- >

summations (0 + 1000000000) 999999999

- >

summation (0 + 1000000000 + 999999999) 999999998

- >

summation (0 + 1000000000 + 999999999 + 999999998) 999999997

Забастовкa >

EDIT: я не видел, что вы скомпилировали с -O2, поэтому выше не происходит. Аккумулятор, даже без каких-либо аннотаций строгости, в большинстве случаев достаточно с правильными уровнями оптимизации.

О нет! Вы храните один миллиард номеров в большом банке, который вы не оцениваете! Tisk! Существует множество решений с использованием аккумуляторов и строгости - кажется, что большинство ответов stackoverflow с чем-либо рядом с этим вопросом будет достаточным, чтобы научить вас в дополнение к библиотечным функциям, таким как fold{l,r}, которые помогут вам избежать написания собственных примитивных рекурсивных функций. Поскольку вы можете посмотреть вокруг и/или спросить об этих концепциях, я перейду к преследованию с этим ответом.

Если вы действительно хотите сделать это правильно, то вы бы использовали список и узнали, что компиляторы Haskell могут выполнять "обезлесение", что означает, что список миллиардов элементов никогда не выделяется:

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 секунд? По умолчанию эти значения являются целыми (целые числа GMP для компилятора GHC) и медленнее, чем машина Int. Используется Int!

% cat x.hs
main = print (sum [0..1000000000] :: Int)
[email protected] /tmp% ghc -O2 x.hs && time ./x
500000000500000000
./x  0.31s user 0.00s system 99% cpu 0.311 total