gpt4 book ai didi

haskell - thunk 使用多少内存?

转载 作者:行者123 更新时间:2023-12-02 11:56:57 24 4
gpt4 key购买 nike

假设我有大量(数百万/数十亿+)这些简单的 Foo 数据结构:

data Foo = Foo
{ a :: {-# UNPACK #-}!Int
, b :: Int
}

由于存在如此多的此类内容,因此有必要考虑它们消耗了多少内存。

在 64 位机器上,每个 Int 为 8 个字节,因此 a 只需要 8 个字节(因为它是严格且未打包的)。但是b会占用多少内存呢?我想这会根据 thunk 是否被评估而改变,对吧?

我想在一般情况下这是不可能判断的,因为 b 可能依赖于任意数量的内存位置,这些位置仅保留在内存中,以防 b 需要进行评估。但是,如果 b 仅依赖于(一些非常昂贵的操作)a 呢?那么,是否有一种确定性的方法来判断将使用多少内存?

最佳答案

除了 user239558 的回答之外,为了回应您的评论,我还想指出一些工具,可以让您检查值的堆表示,自己找到此类问题的答案并查看效果优化和不同的编译方式。

ghc-datasize

告诉你闭包的大小。在这里,您可以看到(在 64 位计算机上)在评估形式和垃圾回收之后,Foo 1 2 本身需要 24 个字节,包括依赖项在内,总共需要 40 个字节:

Prelude GHC.DataSize Test> let x = Foo 1 2Prelude GHC.DataSize Test> xFoo {a = 1, b = 2}Prelude GHC.DataSize Test> System.Mem.performGCPrelude GHC.DataSize Test> closureSize x24Prelude GHC.DataSize Test> recursiveSize x40

To reproduce this you need to load the data definition in compiled form with -O, otherwise, the {-# UNPACK #-} pragma has no effect.

Now let us create a thunk and see that the size goes up considerably:

Prelude GHC.DataSize Test> let thunk = 2 + 3::IntPrelude GHC.DataSize Test> let x = Foo 1 thunkPrelude GHC.DataSize Test> x `seq` return ()Prelude GHC.DataSize Test> System.Mem.performGCPrelude GHC.DataSize Test> closureSize x24Prelude GHC.DataSize Test> recursiveSize x400

Now this is quite excessive. The reason is that this calculation includes references to static closures, Num typeclass dictionaries and the like, and generally the GHCi bytecode is very unoptimized. So let’s put that in a proper Haskell program. Running

main = do
l <- getArgs
let n = length l
n `seq` return ()
let thunk = trace "I am evaluated" $ n + n
let x = Foo 1 thunk
a x `seq` return ()
performGC
s1 <- closureSize x
s2 <- closureSize thunk
r <- recursiveSize x
print (s1, s2, r)

给出(24, 24, 48),所以现在Foo值由Foo本身和一个thunk组成。为什么只有 thunk,不应该还有对 n 添加另外 16 个字节的引用吗?为了回答这个问题,我们需要一个更好的工具:

ghc-heap-view

这个库(由我创建)可以调查堆并准确地告诉您数据在那里的表示方式。因此,将这一行添加到上面的文件中:

buildHeapTree 1000 (asBox x) >>= putStrLn . ppHeapTree

we get (when we pass five parameters to the program) the result Foo (_thunk 5) 1. Note that the order of arguments is swapped on the heap, because pointers always come before data. The plain 5 indicates that the closure of the thunk stores its argument unboxed.

As a last exercise we verify this by making the thunk lazy in n: Now

main = do
l <- getArgs
let n = length l
n `seq` return ()
let thunk = trace "I am evaluated" $ n
let x = Foo 1 thunk
a x `seq` return ()
performGC
s1 <- closureSize x
s2 <- closureSize thunk
s3 <- closureSize n
r <- recursiveSize x
buildHeapTree 1000 (asBox x) >>= putStrLn . ppHeapTree
print (s1, s2, s3, r)

给出 Foo (_thunk (I# 4)) 1 的堆表示,并带有 n 的单独闭包(如 I 的存在所示) # 构造函数)并显示值的预期大小及其总数,(24,24,16,64)

哦,如果这仍然太高,getClosureRaw为您提供原始字节。

关于haskell - thunk 使用多少内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13982863/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com