Ответ 1
Как и когда использовать
SPECIALIZE
прагму и то, что она имеет.
Вы разрешаете компилятору специализировать функцию, если у вас есть полиморфная функция типа (type class), и ожидайте, что она будет вызываться часто в одном или нескольких экземплярах класса (ов).
Специализация удаляет поиск словаря там, где он используется, и часто обеспечивает дальнейшую оптимизацию, тогда функции члена класса могут быть впоследствии объединены, и они подвержены строгому анализу, и дают потенциально огромную прибыль от производительности. Если единственной возможной оптимизацией является устранение дубликарного поиска, коэффициент усиления обычно не будет огромным.
По сравнению с GHC-7, вероятно, более полезно дать функции a {-# INLINABLE #-}
pragma, что делает ее (почти неизменной, некоторую нормализацию и desugaring выполняется) источником доступным в файле интерфейса, поэтому функция может быть специализированной и, возможно, даже встроенный на сайт вызова.
Где использовать
RULES
. Я слышал, что люди, принимающие какое-то правило, не стреляют? Как мы это проверяем?
Вы можете проверить, какие правила были запущены с помощью параметра командной строки -ddump-rule-firings
. Это обычно сбрасывает большое количество запущенных правил, поэтому вам нужно искать немного для своих собственных правил.
Вы используете правила
-
когда у вас более эффективная версия функции для специальных типов, например
{-# RULES "realToFrac/Float->Double" realToFrac = float2Double #-}
-
когда некоторые функции могут быть заменены более эффективной версией для специальных аргументов, например
{-# RULES "^2/Int" forall x. x ^ (2 :: Int) = let u = x in u*u "^3/Int" forall x. x ^ (3 :: Int) = let u = x in u*u*u "^4/Int" forall x. x ^ (4 :: Int) = let u = x in u*u*u*u "^5/Int" forall x. x ^ (5 :: Int) = let u = x in u*u*u*u*u "^2/Integer" forall x. x ^ (2 :: Integer) = let u = x in u*u "^3/Integer" forall x. x ^ (3 :: Integer) = let u = x in u*u*u "^4/Integer" forall x. x ^ (4 :: Integer) = let u = x in u*u*u*u "^5/Integer" forall x. x ^ (5 :: Integer) = let u = x in u*u*u*u*u #-}
-
при переписывании выражения в соответствии с общими законами может возникнуть код, который лучше оптимизировать, например
{-# RULES "map/map" forall f g. (map f) . (map g) = map (f . g) #-}
Обширное использование RULES
в последнем стиле выполняется в фреймовых фреймах, например, в библиотеке text
, и для функция списка в base
, с помощью правил реализуется другой тип слияния (foldr/build
fusion).
Когда делать аргументы функции strict и когда это помогает? Я понимаю, что создание аргумента strict приведет к тому, что аргументы будут оцениваться в нормальной форме, тогда почему я не должен добавлять строгость ко всем аргументам функции? Как я могу решить?
Приведение аргумента strict будет гарантировать, что оно будет оцениваться как слабая головная нормальная форма, а не нормальная форма.
Вы не делаете все аргументы строгими, потому что некоторые функции должны быть нестрогими в некоторых своих аргументах для работы вообще, а некоторые менее эффективны, если они строки во всех аргументах.
Для example partition
должен быть нестрогим во втором аргументе для работы вообще в бесконечных списках, более общая каждая функция, используемая в foldr
, должна быть не строгий во втором аргументе для работы над бесконечными списками. В конечных списках функция, не строгая во втором аргументе, может сделать ее более эффективной (foldr (&&) True (False:replicate (10^9) True)
).
Вы делаете аргумент строгим, если знаете, что аргумент должен быть оценен, прежде чем любая полезная работа может быть выполнена в любом случае. Во многих случаях анализатор строгости GHC может делать это сам по себе, но, конечно, не во всех.
Очень типичным случаем являются аккумуляторы в циклах или хвостовых рекурсиях, где добавление строгости предотвращает создание огромных трюков на пути.
Я не знаю жестких правил, где можно добавить строгость, для меня это вопрос опыта, через какое-то время вы узнаете, в каких местах добавление строгости, вероятно, поможет и где вредит.
Как правило, имеет смысл хранить небольшие данные (например, Int
), но есть исключения.
Как я вижу и проверяю, что у меня есть утечка пространства в моей программе? Каковы общие закономерности, которые представляют собой утечку пространства?
Первым шагом является использование опции +RTS -s
(если программа была связана с включенными rtsopts). Это показывает, сколько памяти было использовано в целом, и вы можете часто судить об этом, есть ли у вас утечка.
Более информативный вывод можно получить при запуске программы с опцией +RTS -hT
, которая создает профиль кучи, который может помочь обнаружить утечку пространства (также необходимо, чтобы программа была связана с включенными rtsopts).
Если требуется дополнительный анализ, программа должна быть скомпилирована с включенным профилированием (-rtsops -prof -fprof-auto
, в старых GHC опция -fprof-auto
недоступна, опция -prof-auto-all
- это самое близкое соответствие).
Затем вы запускаете его с различными параметрами профилирования и смотрите на сгенерированные профили кучи.
Две наиболее распространенные причины утечки пространства:
- слишком много лености
- слишком строгость
третье место, вероятно, связано с нежелательным совместным использованием, GHC делает мало общего исключения подвыражения, но иногда делится длинными списками даже там, где они не нужны.
Чтобы найти причину утечки, я снова не знаю жестких правил, и иногда утечка может быть исправлена путем добавления строгости в одном месте или добавления лени в другую.
Как я вижу, есть ли проблема со слишком ленивой? Я всегда могу проверить профилирование кучи, но я хочу знать, какова общая причина, примеры и модели, где болит ленивость?
Как правило, требуется лень, где результаты можно наращивать постепенно и нежелательно, когда никакая часть результата не может быть доставлена до завершения обработки, как в левых складках или вообще в хвостовых рекурсивных функциях.