5

所以我有一个具有单个严格字段的​​存在数据类型:

data Uncurry (a :: i -> j -> *) (z :: (i,j)) =
  forall x y. z ~ '(x,y) => Uncurry !(a x y) 

使用(从这个答案unsafeSizeof偷来)的实验让我相信它可以是零内存开销:

λ p = (0, '\0') :: (Int, Char)
λ q = Uncurry p
λ unsafeSizeof p
10
λ unsafeSizeof q
10

所以看起来Uncurry有点像 a newtype,只在编译时使用。

这对我来说很有意义,因为平等断言不需要字典。

这是一个有效的解释吗?GHC(或 Haskell 报告)对此有任何保证,还是我只是运气好?

4

2 回答 2

6

data永远不会转换为newtype. Uncurry确实添加了一个新的闭包,并且~从 GHC 8.0.2 开始,实际上还携带了一个字典指针。因此,Uncurry有一个包含三个词的闭包。

unsafeSizeof是不正确的,因为Array#以字ByteArray#为单位存储其大小,而以字节为单位存储其大小,因此sizeofByteArray# (unsafeCoerce# ptrs)返回字数而不是预期的字节数。正确的版本在 64 位系统上如下所示:

unsafeSizeof :: a -> Int
unsafeSizeof !a =
  case unpackClosure# a of
    (# x, ptrs, nptrs #) ->
      I# (8# +# sizeofArray# nptrs *# 8# +# sizeofByteArray# ptrs)

但请注意,unsafeSizeof这只给了我们最顶层闭包的大小。因此,任何盒装元组的闭包大小将为24,这与 的闭包大小一致Uncurry t,因为Uncurry有一个信息指针、一个无用的指针~和一个元组字段的指针。这种巧合也适用于之前的错误unsafeSizeof实现。但总大小Uncurry t大于t

于 2017-09-19T14:09:17.767 回答
5

编辑修复了一些细节:四边形是 8 个字节并解释了静态链接字段。

我认为这unsafeSizeOf是不准确的你误解了它的输出。请注意,它仅显示顶级闭包的内存使用情况,而不是对象的总空间使用情况。我认为,您所看到的是,除了元组之外还需要 10 个字节q(而除了boxed和 boxed之外还需要 10 个字节)。此外,我的测试表明顶级构造函数实际上每个都需要 24 个字节(在 64 位架构上),尽管我也报告了 10 个字节。ppCharIntunsafeSizeOf

特别是,如果我stack ghc -- -fforce-recomp -ddump-asm -dsuppress-all -O2 ZeroMemory.hs使用 GHC 8.0.2 编译以下测试程序:

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}

module ZeroMemory where

data Uncurry (a :: i -> j -> *) (z :: (i, j)) =
  forall x y . z ~ '(x,y) => Uncurry !(a x y)

q :: Uncurry (,) '(Int, Char)
q = Uncurry (0, '\0')

r :: Uncurry (,) '(Int, Char)
r = Uncurry (1, '\1')

那么顶级q闭包的内存占用如下所示:

q_closure:
    .quad   Uncurry_static_info
    .quad   $s$WUncurry_$d~~_closure+1
    .quad   q1_closure+1
    .quad   3

注意.quad这里的每个实际上是8个字节;它是旧式 16 位“字”的“四边形”。我相信quad这里的最后一个值为 3,是GHC 实现注释中描述的“静态链接字段” ,因此不适用于“典型”堆分配对象。

所以,忽略这个 final 字段,顶层q闭包的总大小是 24 个字节,它指的是q1_closure代表包含的元组:

q1_closure:
    .quad   (,)_static_info
    .quad   q3_closure+1
    .quad   q2_closure+1
    .quad   3

另外 24 个字节。

q2q3闭包是装箱的,Int因此Char每个占用两个四边形(16字节)。因此,q总共占用 10 个四边形,即 80 个字节。(我将r其作为健全性检查包括在内,以确保我没有误认任何共享信息。)

一个p元组本身的内存占用相当于q1_closure7 个四边形或 56 个字节。

于 2017-09-19T14:02:19.453 回答