Читаем Учебник по Haskell полностью

определённым значением (undefined). Такие значения называются запакованными (boxed). Незапакованное

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

целых чисел:

data Int = I# Int#

По традиции все незапакованные значения пишутся с решёткой на конце. Запакованные значения позво-

ляют отклдывать вычисления, пользоваться undefined при определении функции. Но за эту гибкость прихо-

дится платить. Вспомним расход памяти в выражении [Pair 1 2]

nil = []

-- глобальный объект (не в счёт)

let x1

= I# 1

-- 2 слова

x2

= I# 2

-- 2 слова

p

= Pair x1 x2

-- 3 слова

val = Cons p nil

-- 3 слова

in

val

------------

-- 10 слов

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

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

нагрузка составляет 2 N. С помощью прагмы UNPACK мы можем отказаться от ленивой гибкости в пользу

меньшего расхода памяти. Эта прагма позволяет встраивать

один конструктор в поле другого. Это поле должно быть строгим (с пометкой ! ) и мономорфным (тип поля

должен быть конкретным типом, а не параметром), причём подчинённый тип должен содержать лишь один

конструктор (у него нет альтернатив):

data PairInt = PairInt

{-# UNPACK #-} !Int

{-# UNPACK #-} !Int

Мы конкретизировали поля Pair и сделали их строгими с помощью восклицательных знаков. После этого

значения из конструктора Int будут храниться прямо в конструкторе PairInt:

nil = []

-- глобальный объект (не в счёт)

let p

= PairInt 1 2

-- 3 слова

val = Cons p nil

-- 3 слова

in

val

------------

-- 6 слов

Так мы сократим размер до 6 N. Но мы можем пойти ещё дальше. Если этот тип является ключевым

типом нашей программы и мы расчитываем на то, что в нём будет хранится много значений мы можем

создать специальный список для таких пар и распаковать значение списка:

data ListInt = ConsInt {-# UNPACK #-} !PairInt

| NilInt

nil = NilInt

let val = ConsInt 1 2 nil

-- 4 слова

in

val

-----------

-- 4 слова

Значение будет встроено дважды и получится, что у нашего нового конструктора Cons уже три поля.

Отметим, что эта прагма имеет смысл лишь при включённом флаге оптимизации -O или выше. Если мы

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

функций вроде

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

sumPair :: PairInt -> Int

sumPair (Pair a b) = a + b

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

компилятор сначала запакует их в конструктор I# и затем применит функцию +, в которой опять распакует

их, сложит и затем, снова запаковав, вернёт результат.

Компилятор автоматически запаковывает все такие значения при передаче в ленивую функцию, это мо-

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

встраивании. Это необходимо учитывать. В таких случая проводите профилирование, убедитесь в том, что

оптимизация привела к повышению эффективности.

В стандартных библиотеках предусмотрено много незапакованных типов. Например это специальные

кортежи. Они пишутся с решётками:

newtype ST s a = ST (STRep s a)

type STRep s a = State# s -> (# State# s, a #)

Это определение типа ST. Специальные кортежи используются для возврата нескольких значений напря-

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

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

UnboxedTuples

Разработчики различных библиотек могут предоставлять несколько вариантов своих данных: ленивые

версии и незапакованные. Например в ST-массив незапакованных значений STUArray s i a эквивалентен

массиву значений в C. В таком массиве можно хранить лишь примитивные типы.

10.8 Краткое содержание

Эта глава была посвящена компилятору GHC. Мы говорим Haskell подразумеваем GHC, говорим GHC

подразумеваем Haskell. К сожалению на данный момент у этого компилятора нет достойных конкурентов.

А может и к счастью, ведь если бы не было GHC, у нас была бы бурная конкуренция среди компиляторов

поплоше. Мы бы не знали, что они не так хороши. Но у нас не было бы программ, которые способны тягаться

по скорости с С. И мы бы говорили: ну декларативное программирование, что поделаешь, за радость аб-

стракций приходится платить. Но есть GHC! Всё-таки это очень трудно: написать компилятор для ленивого

языка

Отметим другие компиляторы: Hugs разработан Марком Джонсом (написан на C), nhc98 основанный

Николасом Райомо (Niklas Röjemo) этот компилятор задумывался как легковесный и простой в установке, он

разрабатывался при поддержке NUTEK, Йоркского университета и Технического университета Чалмерса. От

этого компилятора отпочковался YHC, Йоркский компилятор. UHC – компилятор Утрехтского университета,

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных