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

Недавно в блогах, таких как " Вычисление размера хэш-карты", объяснялось, как рассуждать о пространственных сложностях часто используемых типов контейнеров. Теперь я сталкиваюсь с вопросом о том, как на самом деле "увидеть", какой макет памяти выбирает моя версия GHC (в зависимости от флагов компиляции и целевой архитектуры) для странных типов данных (конструкторов), таких как

data BitVec257 = BitVec257 {-# UNPACK #-} !Word64
                           {-# UNPACK #-} !Word64
                           {-# UNPACK #-} !Bool
                           {-# UNPACK #-} !Word64
                           {-# UNPACK #-} !Word64

data BitVec514 = BitVec514 {-# UNPACK #-} !BitVec257
                           {-# UNPACK #-} !BitVec257

В C есть sizeof а также offsetof оператор, который позволяет мне "увидеть", какой размер и выравнивание был выбран для полей C struct,

Я пытался взглянуть на GHC Core в надежде найти там подсказку, но я не знал, что искать. Может ли кто-нибудь указать мне правильное направление?

2 ответа

Решение

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

{-# LANGUAGE MagicHash,UnboxedTuples #-}
module Size where

import GHC.Exts
import Foreign

unsafeSizeof :: a -> Int
unsafeSizeof a =
  case unpackClosure# a of
    (# x, ptrs, nptrs #) ->
      sizeOf (undefined::Int) + -- one word for the header
        I# (sizeofByteArray# (unsafeCoerce# ptrs)
             +# sizeofByteArray# nptrs)

Используй это:

Prelude> :!ghc -c Size.hs

Size.hs:15:18:
    Warning: Ignoring unusable UNPACK pragma on the
             third argument of `BitVec257'
    In the definition of data constructor `BitVec257'
    In the data type declaration for `BitVec257'
Prelude Size> unsafeSizeof $! BitVec514 (BitVec257 1 2 True 3 4) (BitVec257 1 2 True 3 4)
74

(Обратите внимание, что GHC говорит вам, что не может распаковать Bool так как это тип суммы.)

Вышеупомянутая функция утверждает, что ваш тип данных использует 74 байта на 64-битной машине. Я считаю, что трудно поверить. Я ожидаю, что тип данных будет использовать 11 слов = 88 байт, одно слово на поле. Четное Bools берут одно слово, так как они являются указателями на (статически размещенные) конструкторы. Я не совсем уверен, что здесь происходит.

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

Следы памяти типов данных Haskell

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

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

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

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

data Uno = Uno a
data Due = Due a b

Uno берет 2 слова, а Due - 3.

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

Другие вопросы по тегам