如何找出GHC的数据类型的内存表示?

hvr*_*hvr 15 haskell ghc

最近,诸如计算Hashmap的大小之类的博客条目解释了如何推断常用容器类型的空间复杂性.现在我面临的问题是如何实际"看到"我的GHC版本选择的内存布局(取决于编译标志和目标体系结构),用于奇怪的数据类型(构造函数),例如

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

data BitVec514 = BitVec514 {-# UNPACK #-} !BitVec257
                           {-# UNPACK #-} !BitVec257
Run Code Online (Sandbox Code Playgroud)

在C中有sizeofoffsetof运算符,它允许我"看到"为C字段选择的大小和对齐方式struct.

我试着看看GHC Core希望在那里找到一些提示,但我不知道该找什么.有人能指出我正确的方向吗?

tib*_*bbe 11

我的第一个想法是使用这个整洁的litte功能,由于Simon Marlow:

{-# 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)
Run Code Online (Sandbox Code Playgroud)

使用它:

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
Run Code Online (Sandbox Code Playgroud)

(请注意,GHC告诉您它不能取消装箱,Bool因为它是一种总和类型.)

上述函数声称您的数据类型在64位计算机上使用74个字节.我觉得很难相信.我希望数据类型使用11个字= 88个字节,每个字段一个字.即使Bool是一个词,因为它们是指向(静态分配)构造函数的指针.我不太确定这里发生了什么.

至于对齐,我相信每个字段都应该是字对齐的.

  • 啊,所以我认为该函数有一个bug,因为我们改变了ByteArray#的表示形式(它现在有一个字节长度而不是单词),所以`sizeOfByteArray #`不会乘以字大小.你需要将第一个`sizeOFByteArray#`的结果乘以字大小. (5认同)

sil*_*arf 5

Haskell 数据类型的内存占用

(以下适用于GHC,其他编译器可能使用不同的存储约定)

经验法则:构造函数为标题花费一个词,为每个字段花费一个词。例外:没有字段的构造函数(如 Nothing 或 True)不占用空间,因为 GHC 创建这些构造函数的单个实例并在所有用途中共享它。

一个字在 32 位机器上是 4 个字节,在 64 位机器上是 8 个字节。

所以例如

data Uno = Uno a
data Due = Due a b
Run Code Online (Sandbox Code Playgroud)

Uno 需要 2 个单词,Due 需要 3 个。

此外,我相信可以编写一个执行相同任务的 haskell 函数sizeofoffsetof