haskell-notes

Задать порог величины функции для встраивания можно с помощью флага funfoldinguse

threshold=16. Отметим, что если функция не является экспортируемой и используется лишь один раз,

то GHC втроит её в любом случае, поэтому стоит определять списки экспортируемых определений в шапке

модуля, иначе компилятор будет считать, что экспортируются все определения.

Прагма INLINE может стоять в любом месте, где можно было бы объявить тип значения. Так например

можно указать компилятору встраивать методы класса:

Оптимизация программ | 173

instance Monad T where

{-# INLINE return #-}

return = …

{-# INLINE (>>=) #-}

(>>=)

= …

Встраивание значений может существенно ускорить программу. Но не стоит венчать каждую экспортиру-

емую функцию прагмой INLINE, возможно GHC встроит их автоматически. Посмотреть какие функции были

встроены можно по определениям, попавшим в файл . hi.

Например если мы скомпилируем такой код с флагом ddumphi:

module Inline(f, g) where

g :: Int -> Int

g x = x + 2

f :: Int -> Int

f x = g $ g x

то среди прочих определений увидим:

ghc c ddumphi -O Inline. hs

f :: GHC.Types.Int -> GHC.Types.Int

{- Arity: 1, HasNoCafRefs, Strictness: U(L)m,

Unfolding: InlineRule (1, True, False)

( x :: GHC.Types.Int ->

case x of wild { GHC.Types.I# x1 ->

GHC.Types.I# (GHC.Prim.+# (GHC.Prim.+# x1 2) 2) }) -}

В этом виде прочесть функцию не так просто. Ко всем именам добавлены имена модулей. Приведём

вывод к более простому виду с помощью флага dsuppressall:

ghc c ddumphi dsuppressall -O Inline. hs

f :: Int -> Int

{- Arity: 1, HasNoCafRefs, Strictness: U(L)m,

Unfolding: InlineRule (1, True, False)

( x :: Int -> case x of wild { I# x1 -> I# (+# (+# x1 2) 2) }) -}

Мы видим, что все вызовы функции g были заменены. Если вы всё же подозреваете, что GHC не справ-

ляется с встраиванием ваших часто используемых функций и это сказывается, попробуйте добавить к ним

INLINE, но при этом лучше узнать, привело ли это к росту производительности, проверить с помощью про-

филирования.

Отметим также прагму NOINLINE с её помощью мы можем запретить встраивание функции. Эта праг-

ма часто используется при различных трюках с unsafePerformIO, встраивание функции, которая содержит

неконтролируемые побочные эффекты, может повлиять на её результат.

Прагма RULES

Разработчики GHC хотели, чтобы их компилятор был расширяемым и программист мог бы определять

специфические для его приложения правила оптимизации. Для этого была придумана прагма RULES. За счёт

чистоты функций мы можем в очень простом виде выражать инварианты программы. Инвариант – это неко-

торое свойство значения, которое остаётся постоянным при некоторых преобразованиях. Наиболее распро-

странённые инварианты имеют собственные имена. Например, это коммутативность сложения:

forall a b. a + b = b + a

Здесь мы пишем: для любых a и b изменение порядка следования аргументов у (+) не влияет на результат.

С ключевым словом forall мы уже когда-то встречались, когда говорили о типе ST. Помните тип функции

runST? Пример свойства функции map:

forall f g.

map f . map g = map (f . g)

174 | Глава 10: Реализация Haskell в GHC

Это свойство принято называть дистрибутивностью. Мы видим, что функция композиции дистрибутив-

на относительно функции map. Инварианты определяют скрытые закономерности значений. За счёт чистоты

функций мы можем безболезненно заменить в любом месте программы левую часть на правую или наобо-

рот. Оптимизация начинается тогда, когда мы понимаем, что одна из частей может быть вычислена гораздо

эффективнее другой. Так в примере с map выражение справа от знака равно гораздо эффективнее, поскольку

в нём мы не строим промежуточный список. Особенно ярко разница проявляется в энергичной стратегии

вычислений. Или посмотрим на такое совсем простое свойство:

map id = id

Если мы заменим левую часть на правую, то число сэкономленных усилий будет пропорционально длине

списка. Вряд ли программист станет писать такие выражения, однако они могут появиться после выполнения

других оптимизаций, например после многих встраиваний различных функций.

Можно представить, что эти правила являются дополнительными уравнениями в определении функции:

map f []

= []

map f (x:xs)

= f x : map f xs

map id a

= a

map f (map g x) = map (f . g) x

Словно теперь мы можем проводить сопоставление с образцом не только по конструкторам, но и по выра-

жениям самого языка и функция map стала конструктором. Что интересно, зависимости могут быть какими

угодно, они могут выражать закономерности, присущие той области, которую мы описываем. В дополни-

тельных уравнениях мы подставляем аргументы так же как и в обычных, если где-нибудь в коде программы

находится соответствие с левой частью уравнения, мы заменяем её на правую. При этом мы пишем правила

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

Такие дополнительные правила пишутся в специальной прагме RULES:

{-# RULES

”map/compose”

forall f g x.

map f (map g x)

= map (f . g) x

”map/id”

map id

= id

#-}

Первым в кавычках идёт имя правила. Оно используется только для подсчёта статистики (например ес-

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162