Я читал статью как медленный Haskell в игре с гипотезой Collatz, которая в основном заявляет, что если вы продолжаете умножать три и плюс одно на нечетное число, или разделив четное на два, вы в конечном итоге получите один. Например, 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 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;
}
Компиляция с помощью Clang O2 выполняется на моем компьютере на 0,2 с.
Версия Haskell, приведенная в этой статье, генерирует всю последовательность как список явно, а затем вычисляет длину промежуточного списка. Он в 10 раз медленнее, чем версия C. Однако, поскольку автор использовал LLVM в качестве бэкэнд, который я не установил, я не смог воспроизвести это. Используя GHC 7.8 и бэкэнд по умолчанию, он запускает 10 секунд на моем Mac, что на 50 раз медленнее, чем версия C.
Затем я пишу версию с использованием хвостовой рекурсии и не создавая промежуточный список:
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]]
Скомпилированный с GHC 7.8 и O2, он работает в течение 2 с, меньше на 10 раз, чем версия C.
Интересно, что когда я изменил Int
в аннотации типа на Word
, он потратил только 1, 2 раза быстрее!
Я попытался использовать BangPatterns для явной строгой оценки, но никакого существенного увеличения производительности я не заметил - я полагаю, что строгий анализ GHC достаточно умен, чтобы справиться с таким простым сценарием.
Мои вопросы:
- Почему версия
Word
работает быстрее по сравнению сInt
one? - Почему эта программа Haskell настолько медленная, что в C?