Как узнать представления 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 в надежде найти какой-то намек, но я не знал, что искать. Может ли кто-нибудь указать мне в правильном направлении?
Ответы
Ответ 1
Моя первая идея состояла в том, чтобы использовать эту опрятную функцию litte из-за Саймона Марлоу:
{-# 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
принимают одно слово, поскольку они являются указателями на (статически выделенные) конструкторы. Я не совсем уверен, что происходит здесь.
Что касается выравнивания, я считаю, что каждое поле должно быть выровнено по словам.
Ответ 2
Отпечатки памяти типов данных Haskell
(Следующее относится к GHC, другие компиляторы могут использовать разные соглашения о хранении)
Правило большого пальца: конструктор стоит одно слово для заголовка и одно слово для каждого поля. Исключение: конструктор без полей (например, Nothing или True) не занимает места, потому что GHC создает один экземпляр этих конструкторов и разделяет его среди всех применений.
Слово представляет собой 4 байта на 32-битной машине и 8 байтов на 64-разрядной машине.
Так, например,
data Uno = Uno a
data Due = Due a b
a Uno принимает 2 слова, а функция Due принимает 3.
Также я считаю, что можно написать функцию haskell, которая выполняет те же задачи, что и sizeof
или offsetof