Аномалия производительности Runhaskell
Я пытаюсь понять аномалию производительности, наблюдаемую при запуске программы под runhaskell
,
Рассматриваемая программа:
isFactor n = (0 ==) . (mod n)
factors x = filter (isFactor x) [2..x]
main = putStrLn $ show $ sum $ factors 10000000
Когда я запускаю это, это занимает 1,18 секунды.
Тем не менее, если я переопределить isFactor
как:
isFactor n f = (0 ==) (mod n f)
тогда программа занимает 17,7 секунды.
Это огромная разница в производительности, и я ожидаю, что программы будут эквивалентны. Кто-нибудь знает, что мне здесь не хватает?
Примечание. Этого не происходит при компиляции в GHC.
2 ответа
Хотя функции должны быть идентичны, есть разница в том, как они применяются. С первым определением isFactor
полностью применяется на сайте вызова isFactor x
, Во втором определении это не так, потому что сейчас isFactor
явно принимает два аргумента.
Даже минимальной оптимизации достаточно для того, чтобы GHC видел это и создавал идентичный код для обоих, однако, если вы компилируете с -O0 -ddump-simpl
вы можете определить, что без оптимизации это имеет значение (по крайней мере, для ghc-7.2.1, YMMV с другими версиями).
С первым isFactor
GHC создает одну функцию, которая передается в качестве предиката в "GHC.List.Filter", с вызовами mod 10000000
а также (==)
встраиваемый. Что касается второго определения, то вместо этого происходит то, что большинство вызовов в пределах isFactor
являются связанными по буквам ссылками на функции класса и не разделяются между несколькими вызовами isFactor
, Так что есть много словарных накладных расходов, которые совершенно не нужны.
Это почти никогда не проблема, потому что даже настройки компилятора по умолчанию будут оптимизировать его, однако runhaskell, по-видимому, даже не делает этого. Тем не менее, я иногда структурировал код как someFun x y = \z ->
потому что я знаю это someFun
будет применяться частично, и это был единственный способ сохранить совместное использование вызовов (т. е. оптимизатор GHC не был достаточно умным).
Как я понимаю, runhaskell
мало что делает для оптимизации. Он предназначен для быстрой загрузки и запуска кода. Если бы он выполнял больше оптимизации, ваш код начал бы работать дольше. Конечно, в этом случае код работает быстрее с оптимизацией.
Насколько я понимаю, если существует скомпилированная версия кода, то runhaskell
буду использовать это. Поэтому, если для вас важна производительность, просто убедитесь, что вы компилируете с включенной оптимизацией. (Я думаю, что вы могли бы даже передать переключатели runhaskell
чтобы включить оптимизацию - вам нужно проверить документацию...)