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