Объем памяти типов данных Haskell

Как я могу найти фактический объем памяти, необходимый для хранения значения некоторого типа данных в Haskell (в основном с GHC)? Можно ли оценить его во время выполнения (например, в GHCi) или можно ли оценить требования к памяти составного типа данных из его компонентов?

В общем случае, если известны требования к памяти типов a и b, каковы издержки памяти для типов алгебраических данных, такие как:

data Uno = Uno a
data Due = Due a b

Например, сколько байтов в памяти занимают эти значения?

1 :: Int8
1 :: Integer
2^100 :: Integer
\x -> x + 1
(1 :: Int8, 2 :: Int8)
[1] :: [Int8]
Just (1 :: Int8)
Nothing

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

Я обнаружил, что есть опция :set +s в GHCi для просмотра статистики памяти, но неясно, как оценить размер памяти одного значения.

Ответ 1

(Следующее относится к GHC, другие компиляторы могут использовать разные соглашения о хранении)

Правило большого пальца: конструктор стоит одно слово для заголовка и одно слово для каждого поля. Исключение: конструктор без полей (например, Nothing или True) не занимает места, потому что GHC создает один экземпляр этих конструкторов и разделяет его среди всех применений.

Слово представляет собой 4 байта на 32-битной машине и 8 байтов на 64-разрядной машине.

Так, например,

data Uno = Uno a
data Due = Due a b

a Uno принимает 2 слова, a Due принимает 3.

Тип Int определяется как

data Int = I# Int#

теперь Int# берет одно слово, поэтому Int занимает всего 2. Большинство распакованных типов принимают одно слово, исключения - Int64#, Word64# и Double# (на 32-разрядной машине), которые принимают 2. GHC на самом деле имеет кэш небольших значений типа Int и Char, поэтому во многих случаях они не занимают кучи. A String требуется только пространство для ячеек списка, если вы не используете Char > 255.

An Int8 имеет идентичное представление Int. Integer определяется следующим образом:

data Integer
  = S# Int#                            -- small integers
  | J# Int# ByteArray#                 -- large integers

поэтому маленький Integer (S#) принимает 2 слова, но большое целое число принимает переменное количество пространства в зависимости от его значения. A ByteArray# принимает 2 слова (заголовок + размер) плюс пространство для самого массива.

Обратите внимание, что конструктор, определенный с помощью newtype, свободен. newtype - это просто идея времени компиляции, и она не занимает места и не требует инструкций во время выполнения.

Подробнее в Макет кучных объектов в комментарии GHC.

Ответ 2

Пакет ghc-datasize предоставляет функцию recursiveSize для вычисления размера объекта GHC. Однако...

Сбор мусора выполняется до расчета размера, потому что сборщик мусора затруднит выполнение кучи.

... так что было бы нецелесообразно часто называть это!

Также см. Как узнать представления памяти GHC типов данных? и Как определить размер типа в Haskell?.