Когда использовать различные языковые прагмы и оптимизацию?

У меня есть немного понимания haskell, но я всегда не уверен, какие прагмы и оптимизации я должен использовать и где. Как

  • Как и когда использовать SPECIALIZE прагму и то, что она имеет.
  • Где использовать RULES. Я слышал, что люди, принимающие какое-то правило, не стреляют? Как мы это проверяем?
  • Когда делать аргументы функции strict и когда это помогает? Я понимаю, что создание аргумента strict приведет к тому, что аргументы будут оцениваться в нормальной форме, тогда почему я не должен добавлять строгость ко всем аргументам функции? Как мне решить?
  • Как я вижу и проверяю, что у меня есть утечка пространства в моей программе? Каковы общие закономерности, которые представляют собой утечку пространства?
  • Как я вижу, есть ли проблема со слишком ленивой? Я всегда могу проверить профилирование кучи, но я хочу знать, какова общая причина, примеры и шаблоны, где болит ленивость?

Есть ли какой-либо источник, который говорит о передовых оптимизации (как на более высоких, так и на очень низких уровнях), особенно в отношении haskell?

Ответы

Ответ 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 делает мало общего исключения подвыражения, но иногда делится длинными списками даже там, где они не нужны.

Чтобы найти причину утечки, я снова не знаю жестких правил, и иногда утечка может быть исправлена ​​путем добавления строгости в одном месте или добавления лени в другую.

Как я вижу, есть ли проблема со слишком ленивой? Я всегда могу проверить профилирование кучи, но я хочу знать, какова общая причина, примеры и модели, где болит ленивость?

Как правило, требуется лень, где результаты можно наращивать постепенно и нежелательно, когда никакая часть результата не может быть доставлена ​​до завершения обработки, как в левых складках или вообще в хвостовых рекурсивных функциях.

Ответ 2

Я рекомендую прочитать документацию GHC на Pragmas и Переписать правила, поскольку они затрагивают многие ваши вопросы о СПЕЦИАЛИЗАЦИИ и ПРАВИЛАХ.

Кратко рассмотрите свои вопросы:

  • SPECIALIZE используется, чтобы заставить компилятор создать специализированную версию полиморфной функции для определенного типа. Преимущество заключается в том, что применение функции в этом случае больше не потребует словаря. Недостатком является то, что он увеличит размер вашей программы. Специализация особенно ценна для функций, называемых "внутренними петлями", и она по существу бесполезна для нередко называемых функций верхнего уровня. Обратитесь к Документация GHC для взаимодействия с INLINE.

  • ПРАВИЛА позволяют вам указывать правила перезаписи, которые, как известно, действительны, но компилятор не мог сделать вывод самостоятельно. Общим примером является {-# RULES "mapfusion" forall f g xs. map f (map g xs) = map (f.g) xs #-}, который сообщает GHC, как слить map. Может быть очень сложно заставить GHC использовать правила из-за вмешательства в INLINE. 7.19.3 затрагивает вопрос о том, как избежать конфликтов, а также как заставить GHC использовать правило, даже если оно обычно избегайте этого.

  • Строгие аргументы наиболее важны для чего-то вроде аккумулятора в хвостовой рекурсивной функции. Вы знаете, что значение в конечном счете будет полностью рассчитано, а создание стека замыканий для задержки вычисления полностью победит цель. Естественно, следует избегать насильственной строгости в любое время, когда функция может применяться к значению, которое должно обрабатываться лениво, подобно бесконечному списку. Как правило, лучшая идея состоит в том, чтобы изначально только установить строгость, где она, очевидно, полезна (например, аккумуляторы), а затем добавить позже, только когда профилирование показывает, что это необходимо.

  • Мой опыт заключается в том, что большинство демонстрационных утечек пространства прекратились из ленивых аккумуляторов и неоценимых ленивых значений в очень больших структурах данных, хотя я уверен, что это относится к типам программ, которые вы пишете. Использование беспорядочных структур данных по мере возможности устраняет многие проблемы.

  • Вне случаев, когда лень вызывает утечку пространства, основную ситуацию, в которой следует избегать, заключается в IO. Ленецкий ресурс обработки по своей сути увеличивает количество времени настенных часов, необходимое для ресурса. Это может быть плохо для производительности кеша, и это явно плохо, если что-то еще хочет эксклюзивных прав на использование одного и того же ресурса.