нагрузка составляет 2 N . С помощью прагмы UNPACKмы можем отказаться от ленивой гибкости в пользу
меньшего расхода памяти. Эта прагма позволяет встраивать
один конструктор в поле другого. Это поле должно быть строгим (с пометкой !) и мономорфным (тип поля
должен быть конкретным типом, а не параметром), причём подчинённый тип должен содержать лишь один
конструктор (у него нет альтернатив):
data PairInt = PairInt
{-# UNPACK #-} !Int
{-# UNPACK #-} !Int
Мы конкретизировали поля Pairи сделали их строгими с помощью восклицательных знаков. После этого
значения из конструктора Intбудут храниться прямо в конструкторе PairInt:
nil = []
-- глобальный объект (не в счёт)
letp
= PairInt1 2
-- 3 слова
val = Consp nil
-- 3 слова
in
val
------------
-- 6 слов
Так мы сократим размер до 6 N . Но мы можем пойти ещё дальше. Если этот тип является ключевым
типом нашей программы и мы расчитываем на то, что в нём будет хранится много значений мы можем
создать специальный список для таких пар и распаковать значение списка:
data ListInt = ConsInt{-# UNPACK #-} !PairInt
| NilInt
nil = NilInt
letval = ConsInt1 2 nil
-- 4 слова
in
val
-----------
-- 4 слова
Значение будет встроено дважды и получится, что у нашего нового конструктора Consуже три поля.
Отметим, что эта прагма имеет смысл лишь при включённом флаге оптимизации -Oили выше. Если мы
не включим этот флаг, то компилятор не будет проводить встраивание функций, поэтому при вычислении
функций вроде
Оптимизация программ | 177
sumPair :: PairInt -> Int
sumPair ( Paira b) =a +b
Плюс не будет встроен и вместо того, чтобы сразу сложить два числа с помощью примитивной функции,
компилятор сначала запакует их в конструктор I# и затем применит функцию +, в которой опять распакует
их, сложит и затем, снова запаковав, вернёт результат.
Компилятор автоматически запаковывает все такие значения при передаче в ленивую функцию, это мо-
жет привести к снижению быстродействия даже при включённом флаге оптимизации, при недостаточном
встраивании. Это необходимо учитывать. В таких случая проводите профилирование, убедитесь в том, что
оптимизация привела к повышению эффективности.
В стандартных библиотеках предусмотрено много незапакованных типов. Например это специальные
кортежи. Они пишутся с решётками:
newtype STs a = ST( STReps a)
type STReps a = State#s ->( # State#s, a #)
Это определение типа ST. Специальные кортежи используются для возврата нескольких значений напря-
мую, без создания промежуточного кортежа в куче. В этом случае значения будут сохранены в регистрах
или на стеке. Для использования специальных значений необходимо активировать расширения MagicHashи
UnboxedTuples
Разработчики различных библиотек могут предоставлять несколько вариантов своих данных: ленивые
версии и незапакованные. Например в ST-массив незапакованных значений STUArrays i a эквивалентен
массиву значений в C. В таком массиве можно хранить лишь примитивные типы.
10.8 Краткое содержание
Эта глава была посвящена компилятору GHC. Мы говорим Haskell подразумеваем GHC, говорим GHC
подразумеваем Haskell. К сожалению на данный момент у этого компилятора нет достойных конкурентов.
А может и к счастью, ведь если бы не было GHC, у нас была бы бурная конкуренция среди компиляторов
поплоше. Мы бы не знали, что они не так хороши. Но у нас не было бы программ, которые способны тягаться
по скорости с С. И мы бы говорили: ну декларативное программирование, что поделаешь, за радость аб-
стракций приходится платить. Но есть GHC! Всё-таки это очень трудно: написать компилятор для ленивого
языка
Отметим другие компиляторы: Hugs разработан Марком Джонсом (написан на C), nhc98 основанный
Николасом Райомо (Niklas Röjemo) этот компилятор задумывался как легковесный и простой в установке, он
разрабатывался при поддержке NUTEK, Йоркского университета и Технического университета Чалмерса. От
этого компилятора отпочковался YHC, Йоркский компилятор. UHC – компилятор Утрехтского университета,
разработан для тестирования интересных идей в теории типов. JHC (Джон Мичэм, John Meacham) и LHC
Читать дальше