Ответ 1
Ваша версия Haskell создает ленивый список в prime
только для проверки, является ли он нулевым. Кажется, это действительно бутылочная шее. Следующая версия работает так же быстро, как и версия С++ на моей машине:
prime :: Int -> Bool
prime x = go 3
where
go q | q <= isqrt x = if rem x q == 0 then False else go (q+2)
go _ = True
3.31s при компиляции с -O2 против 3.18s для С++ с gcc 4.8 и -O3 для n = 5000000.
Конечно, "угадать", где программа медленно оптимизирует, это не очень хороший подход. К счастью, у Haskell есть хорошие инструменты для профилирования на борту.
Компилирование и запуск с помощью
$ ghc --make primes.hs -O2 -prof -auto-all -fforce-recomp && ./primes 5000000 +RTS -p
дает
# primes.prof
Thu Feb 20 00:49 2014 Time and Allocation Profiling Report (Final)
primes +RTS -p -RTS 5000000
total time = 5.71 secs (5710 ticks @ 1000 us, 1 processor)
total alloc = 259,580,976 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
prime.go Main 96.4 0.0
main Main 2.0 84.6
isqrt Main 0.9 15.4
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 45 0 0.0 0.0 100.0 100.0
main Main 91 0 2.0 84.6 100.0 100.0
prime Main 92 2500000 0.7 0.0 98.0 15.4
prime.go Main 93 326103491 96.4 0.0 97.3 15.4
isqrt Main 95 0 0.9 15.4 0.9 15.4
--- >8 ---
который ясно показывает, что prime
- это то, где вещи становятся горячими. Для получения дополнительной информации о профилировании я отсылаю вас к Real World Haskell, Chap 25.
Чтобы понять, что происходит, вы можете посмотреть (один из) промежуточных языков GHC Core, который покажет вам, как выглядит код после оптимизации. Некоторая хорошая информация в Haskell wiki. Я бы не рекомендовал делать это, если это необходимо, но хорошо знать, что существует такая возможность.
К вашим другим вопросам:
1) Как, не изменяя алгоритм, я могу оптимизировать реализацию Haskell?
Профиль и попытайтесь написать внутренние циклы, чтобы они не делали выделения памяти и могут быть сделаны строгими с помощью компилятора. Это может привести к некоторой практике и опыту.
2) Действительно ли Haskell по соотношению производительности с C?
Это зависит. GHC поражает и часто может оптимизировать вашу программу очень хорошо. Если вы знаете, что делаете, вы можете приблизиться к производительности оптимизированного C (100% - 200% от скорости C). Тем не менее, эти оптимизации не всегда легки или красивы для глаз, и высокий уровень Haskell может быть медленнее. Но не забывайте, что при использовании Haskell вы получаете потрясающую выразительность и абстракции высокого уровня. Обычно он будет достаточно быстрым для всех, кроме приложений с высокой критичностью производительности, и даже тогда вы часто можете приблизиться к C с некоторой оптимизацией и оптимизацией производительности.