作为一个名为“beercan”的爱好项目,我正在对Torchlight游戏的资源文件进行逆向工程。使用okay-ish十六进制编辑器,我尝试猜测文件的结构,然后我对我的想法进行建模,使用cereal
来编写Get
ters(以及后来的一些Put
ters) ,并尝试decode
库中应用程序中的每个文件。
我刚开始使用Torchlight编译的布局文件(TL1中为*.LAYOUT
,TL2中为*.LAYOUT.cmp
)。格式结果比dat文件有点棘手,但我想我找出了basic structure和how they are encoded in the TL2 files。所以我正在尝试制作文件版本,标签号和猜测数据类型的地图。
为此,我编写了an application来展平数据结构,只留下叶子值的猜测类型,每个叶子都注释了文件版本以及节点和叶标签号。我将其转换为从文件版本和标签号到一组猜测类型的映射。 对于每个文件,我希望这个Map可能会占用内存中文件大小的两倍。(虽然不确定。)然后,我合并这些地图,然后打印地图。
出于某种原因,即使我只需要20MB的文件(100个文件),内存使用量也会线性增加到大约200MB,然后减少到生成的地图的最终大小,然后在打印时快速放气。 / p>
我不希望这种内存使用。有谁知道我怎么能解决它?我试图在解码后强制使用值(使用deepseq),我尝试将数据类型添加到数据类型,但这并没有真正帮助。我已经尝试复制我保留在文件结构中的所有字节串,这会降低内存使用量,但它仍然高得令人无法接受,特别是当我想分析整个数据集(200MB +原始文件)时。
-edit-我推动了一个(不是非常S)SCCE来演示性能问题,(不小心)以及我的分析结果。
cabal configure
,带有启用性能分析的标志(需要--enable-library-profiling --enable-executable-profiling --ghc-options="-rtsopts -prof"
是正常的吗?)cabal build
cd test
,并运行StressTest.sh
。此脚本尝试加载常规TL2布局文件100次。在我的机器上,top
表示它需要大约500MB的内存,并且分析结果与我上面的描述一致。
答案 0 :(得分:1)
我完全同意@petrpudlak,我们需要实际代码才能对“为什么我的代码使用如此多的内存?”这一问题做出任何有意义的评论。 :)(对不起,你确实提供了代码),但是,你描述的一些模式在Haskell中非常典型,并且可以进行一些通用的讨论。
首先,请注意本机Haskell类型使用的内存比您猜测的要多得多。看一下http://www.haskell.org/haskellwiki/GHC/Memory_Footprint的ghc内存占用页面。请注意,即使是简单的Char也会占用整个16字节的内存!在String中添加指向链接列表项的指针,您可以轻松地使用比您猜测的内存大一个数量级的内存。如果内存很重要,你应该使用另一种数据类型,比如Data.Text或Data.ByteString,它在内部存储字符串更像c will(作为内存中的字节块,每个字符1-4个字节,具体取决于编码和使用什么字符)。如果问题不是字符串数据,则可以将未装箱的数组用于任意数据类型。
其次,如果可能的话,您可以通过处理串联项目来减少内存使用量(内存将立即被垃圾收集)。 Haskell懒惰经常会自动为您执行此操作,例如,尝试运行以下程序
import Data.Char
main = interact $ map toUpper
当你输入时,输出会连续显示(你的操作系统,而不是Haskell,可能会缓冲整行,所以你可能需要在看到任何内容之前点击'enter',但你会看到每个'enter'的输出更新)。不是将整个输入加载到内存中然后一次处理所有内容,而是创建Char内存并通过Char对垃圾进行收集。
当然这并不总是可行的(即 - 如果你必须以非本地方式处理数据),但大多数时候至少部分代码可以通过这种方式重构以减少总内存使用量
编辑 - 抱歉,我刚刚意识到您确实发布了代码的链接,并且您正在使用ByteString .....所以我写的一些内容无效。但我仍然看到盒装列表和ByteString的解包,所以我会保留答案。
答案 1 :(得分:0)
内存使用模式听起来像你的应用程序正在构建大量不必要的thunks,然后当那些thunk被评估时内存消耗开始下降。我只是快速浏览了您的代码,但您可以尝试的一个简单更改是将Data.Map
的所有导入替换为Data.Map.Strict
。如果您在Map中对值进行大量更新而不强制进行评估,则这一点尤为重要。
您应该注意的另一件事是replicateM
在严格的monad中使用较大的数字是非常低效的(参见例如this answer)。我不确定您在申请中通常会处理什么样的重要事项,但请记住这一点很好。
在LeafValue
类型等简单容器数据类型中使用严格字段也可能会有所帮助,并使用-funbox-strict-fields
(当然还有-O2
)进行编译。