case
-выражения и декомпозиция в аргументах функции могут стать источником очень неприятных оши-бок. Программа прошла проверку типов, завелась и вот уже работает-работает как вдруг мы видим на экране:
*** Exception: Prelude.
head: empty listили
*** Exception: Maybe.
fromJust: NothingИ совсем не понятно откуда эта ошибка. В каком модуле сидит эта функция. Может мы её импортировали
из чужой библиотеки или написали сами. Как раз для таких случаев в GHC предусмотрен специальный флаг
xc.
Посмотрим на выполнение такой программы:
Статистика выполнения программы | 171
leak2 6 +RTS -hc
5,944 bytes x seconds
Fri Jun 1 21:51 2012
bytes
30k
(51)PINNED
25k
20k
(72)GHC.IO.Encoding.CAF
15k
(59)GHC.IO.Handle.FD.CAF
10k
(58)GHC.Conc.Signal.CAF
5k
0k
0.0
0.0
0.0
0.1
0.1
0.1
0.1
0.1
0.2
0.2
0.2
seconds
Рис. 10.14: Профиль кучи без утечки памяти
module Main where
addEvens :: Int -> Int -> Int
addEvens a b
|
even a && even b = a + bq =
zipWith addEvens [0, 2, 4, 6, 7, 8, 10] (repeat 0)main =
print qДля того, чтобы воспользоваться флагом xc необходимо скомпилировать программу с возможностью про-
филирования:
$ ghc --make break.hs -rtsopts -prof
$ ./break +RTS -xc
*** Exception (reporting due to +RTS -xc): (THUNK_2_0), stack trace:
Main.CAF
break: break.hs:(4,1)-(5,30): Non-exhaustive patterns in function addEvens
Так мы узнали в каком месте кода проявился злосчастный вызов, это строки (4,1)-
(5,30). Что соот-ветствует определению функции addEvens. Не очень полезная информация. Мы и так бы это узнали. Нам
бы хотелось узнать тот путь, по которому шла программа к этому вызову. Проблема в том, что все вызовы
слились в один CAF
для модуля. Так разделим их:$ ghc --make break.hs -rtsopts -prof -caf-all -auto-all
$ ./break +RTS -xc
*** Exception (reporting due to +RTS -xc): (THUNK_2_0), stack trace:
Main.addEvens,
called from Main.q,
called from Main.CAF:q
--> evaluated by: Main.main,
called from :Main.CAF:main
break: break.hs:(4,1)-(5,30): Non-exhaustive patterns in function addEvens
Теперь мы видим путь к этому вызову, мы пришли в него из знчения q, которое было вызвано из main.
10.7 Оптимизация программ
В этом разделе мы поговорим о том этапе компиляции, на котором происходят преобразования Core ->
Core
. Мы называли этот этап упрощением программы.172 | Глава 10: Реализация Haskell в GHC
Флаги оптимизации
Мы можем задавать степень оптимизации программы специальными флагами. Самые простые флаги на-
чинаются с большой буквы O
. Естесственно, чем больше мы оптимизируем, тем дольше компилируется код.Поэтому не стоит увлекаться оптимизацией на начальном этапе проектирования. Посмотрим какие возмож-
ности у нас есть:
• без -O
– минимум оптимизаций, код компилируется как можно быстрее.• -O0
– выключить оптимизацию полностью• -O
– умеренная оптимизация.• O2
– активная оптимизация, код компилируется дольше, но пока O2 не сильно выигрывает у O по про-дуктивности.
Для оптимизации мы компилируем программу с заданным флагом, например попробуйте скомпилиро-
вать самый первый пример с флагом O
:ghc --make sum.hs -O
и утечка памяти исчезнет.
Посмотреть описание конкретных шагов оптимизации можно в документации к GHC. Например при вклю-
чённой оптимизации GHC применяет анализ строгости. В ходе него GHC может исправить простые утечки
памяти за нас. Стоит отметить оптимизацию -
fexcess-precision, он может существенно ускорить програм-мы, в которых много вычислений с Double
. Но при этом вычисления могут потерять в точности, округлениестановится непредсказуемым.
Прагма INLINE
Если мы посмотрим в исходный файл для модуля Prelude
, то мы найдём такое определение для компо-зиции функций:
-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.
)::
(b -> c) -> (a -> b) -> a -> c(.
) f g = \x -> f (g x)Помимо знакомого нам определения и комментариев мы видим новую прагму INLINE
. Она указываеткомпилятору на то, что на этапе упрощения программы необходимо заменить вызов функции на её правую
часть. Этот процесс называют встраиванием функций. Замена будет произведена только в случае полного
применения функции, если синтаксическая арность (количество аргументов слева от знака равно) совпадает
с числом переданных в функцию аргументов. Поэтому для GHC есть существенная разница между определе-
ниями:
(.
) f g = \x -> f (g x)(.
) f g x = f (g x)Встраиванием функций мы экономим на создании лишних объектов в куче, но при этом код может су-
щественно разбухнуть. GHC пользуется эвристическим алгоритмом при определении когда функцию стоит
встраивать, а когда – нет. По умолчанию GHC проводит встраивание только внутри модуля. Если мы компи-
лируем с флагом O
, функции будут встраиваться между модулями. Для этого GHC сохраняет в интерфейсномфайле (с расширением .
hi) не только типы функций, но и павые части достаточно кратких функций. Дли-