Как узнать, как 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 в надежде найти там подсказку, но не знал, что искать. Может ли кто-нибудь указать мне правильное направление?


person hvr    schedule 04.07.2011    source источник
comment
Какая у тебя мотивация? Чистое любопытство, или вы пытаетесь взаимодействовать с другим языком или чем-то еще?   -  person dave4420    schedule 04.07.2011
comment
Да, в основном из любопытства. Я хочу иметь возможность проверить, действительно ли GHC делает то, что я ожидаю / предполагаю ... или мне нужно исправить свои предположения ... :-)   -  person hvr    schedule 05.07.2011


Ответы (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 принимают одно слово, поскольку они являются указателями на (статически размещенные) конструкторы. Я не совсем понимаю, что здесь происходит.

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

person tibbe    schedule 04.07.2011
comment
Итак, я думаю, что в этой функции есть ошибка, так как мы изменили представление ByteArray # (теперь у него длина в байтах, а не в словах), и поэтому sizeOfByteArray# не умножается на размер слова. Вам нужно умножить результаты первого sizeOFByteArray# на размер слова. - person Simon Marlow; 06.07.2011
comment
кстати, что на самом деле представляют собой ptrs и nptrs? - person hvr; 08.07.2011

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

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

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

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

So e.g.

data Uno = Uno a
data Due = Due a b

Uno занимает 2 слова, а Due - 3.

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

person silleknarf    schedule 04.07.2011
comment
О, спасибо, не видел другого ТАК вопроса. Но на самом деле меня не так сильно интересует «практическое правило», я хочу иметь возможность декодировать фактический объем памяти. Я предполагаю, что GHC должен каким-то образом хранить используемый макет памяти, чтобы иметь возможность соответствующим образом упорядочивать данные, возможно, фактический макет памяти, хранящийся в этих .hi файлах? - person hvr; 04.07.2011
comment
Это эмпирическое правило действительно точно в полиморфном случае. Это только усложняется, если вы начинаете {- # UNPACK # -} или имеете дело с полями типа #, некоторые из них могут занимать более одного слота для хранения, например, Double # или Word64 # на 32-битной платформе. - person Edward KMETT; 05.07.2011