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

124

Как я могу найти фактический объем памяти, необходимый для хранения значения некоторого типа данных в 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 есть опция для просмотра статистики памяти, но неясно, как оценить объем памяти для одного значения.

sastanin
источник

Ответы:

156

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

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

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

Так например

data Uno = Uno a
data Due = Due a b

an Unoзанимает 2 слова, а a - Due3.

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

data Int = I# Int#

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

Представление Int8у An идентично Int. Integerопределяется так:

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

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

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

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

Саймон Марлоу
источник
1
Спасибо, Саймон. Это именно то, что я хотел знать.
sastanin
2
Разве заголовок не два слова? Один для тега, а другой для указателя пересылки для использования во время сборки мусора или оценки? Так разве это не прибавит к общему количеству ни одного слова?
Эдвард КМЕТТ
5
@Edward: Преобразователи перезаписываются косвенными указаниями (которые позже удаляются сборщиком мусора), но это всего лишь 2 слова, и каждый объект кучи гарантированно имеет размер не менее двух 2-х слов. Без включения каких-либо функций профилирования или отладки заголовок на самом деле состоит только из одного слова. То есть в GHC другие реализации могут действовать иначе.
nominolo
3
nominolo: да, но из Closure.h: / * Преобразователь имеет слово-заполнитель, чтобы принять обновленное значение. Это сделано для того, чтобы обновление не перезаписывало полезную нагрузку, поэтому нам не нужно блокировать преобразователь во время ввода и обновления. Примечание: это не относится к THUNK_STATIC, которые не имеют полезной нагрузки. Примечание: мы оставляем это слово для заполнения во всех случаях, а не только в SMP, чтобы нам не пришлось перекомпилировать все наши библиотеки для SMP. * / Полезная нагрузка не перезаписывается во время косвенного обращения. Обращение написано в отдельном месте в заголовке.
Эдвард КМЕТТ
6
Да, но учтите, что это только для преобразователей . К конструкторам это не относится. В любом случае оценить размер преобразователя немного сложно - вам нужно посчитать свободные переменные.
nominolo
4

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

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

... поэтому было бы непрактично звонить так часто!

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

mhwombat
источник