在序列化期间提高内存使用量(Data.Binary)

时间:2016-04-12 16:33:22

标签: haskell memory serialization memory-leaks

我仍然是Haskell的新手,每天都在学习新事物。我的问题是在使用Data.Binary库进行seralization期间内存使用率过高。也许我只是以错误的方式使用库,但我无法弄明白。

实际的想法是,我从磁盘读取二进制数据,添加新数据并将所有内容写回磁盘。这是代码:

module Main
  where

import Data.Binary
import System.Environment
import Data.List (foldl')

data DualNo = DualNo Int Int deriving (Show)

instance Data.Binary.Binary DualNo where
  put (DualNo a b) = do
    put a
    put b
  get = do
    a <- get
    b <- get
    return (DualNo a b)

-- read DualNo from HDD
readData :: FilePath -> IO [DualNo]
readData filename = do
  no <- decodeFile filename :: IO [DualNo]
  return no

-- write DualNo to HDD
writeData  :: [DualNo] -> String -> IO ()
writeData no filename = encodeFile filename (no :: [DualNo])

writeEmptyDataToDisk :: String -> IO ()
writeEmptyDataToDisk filename = writeData [] filename

-- feed a the list with a new dataset
feedWithInputData :: [DualNo] -> [(Int, Int)] -> [DualNo]
feedWithInputData existData newData = foldl' func existData newData
  where
    func dataset (a,b) = DualNo a b : dataset

main :: IO ()
main = do
  [newInputData, toPutIntoExistingData] <- System.Environment.getArgs
  if toPutIntoExistingData == "empty"
    then writeEmptyDataToDisk "myData.dat"
    else return ()
  loadedData <- readData "myData.dat"
  newData <- return (case newInputData of
                        "dataset1" -> feedWithInputData loadedData dataset1
                        "dataset2" -> feedWithInputData loadedData dataset2
                        otherwise  -> feedWithInputData loadedData dataset3)
  writeData newData "myData.dat"

dataset1 = zip [1..100000]    [2,4..200000]
dataset2 = zip [5,10..500000] [3,6..300000]
dataset3 = zip [4,8..400000]  [6,12..600000]

我很确定,这段代码还有很多需要改进的地方。但我最大的问题是大数据集的内存使用情况。

我用GHC描述了我的程序。

$ ghc -O2 --make -prof -fprof-auto -auto-all -caf-all -rtsopts -fforce-recomp Main.hs
$ ./Main dataset1 empty +RTS -p -sstderr
     165,085,864 bytes allocated in the heap
      70,643,992 bytes copied during GC
      12,298,128 bytes maximum residency (7 sample(s))
         424,696 bytes maximum slop
              35 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0       306 colls,     0 par    0.035s   0.035s     0.0001s    0.0015s
  Gen  1         7 colls,     0 par    0.053s   0.053s     0.0076s    0.0180s

  INIT    time    0.001s  (  0.001s elapsed)
  MUT     time    0.059s  (  0.062s elapsed)
  GC      time    0.088s  (  0.088s elapsed)
  RP      time    0.000s  (  0.000s elapsed)
  PROF    time    0.000s  (  0.000s elapsed)
  EXIT    time    0.003s  (  0.003s elapsed)
  Total   time    0.154s  (  0.154s elapsed)

  %GC     time      57.0%  (57.3% elapsed)

  Alloc rate    2,781,155,968 bytes per MUT second

  Productivity  42.3% of total user, 42.5% of total elapsed

查看prof-file:

    Tue Apr 12 18:11 2016 Time and Allocation Profiling Report  (Final)

       Main +RTS -p -sstderr -RTS dataset1 empty

    total time  =        0.06 secs   (60 ticks @ 1000 us, 1 processor)
    total alloc = 102,613,008 bytes  (excludes profiling overheads)

COST CENTRE            MODULE    %time %alloc

put                    Main       48.3   53.0
writeData              Main       30.0   18.8
dataset1               Main       13.3   23.4
feedWithInputData      Main        6.7    0.0
feedWithInputData.func Main        1.7    4.7


                                                                    individual     inherited
COST CENTRE               MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN                      MAIN                     68           0    0.0    0.0   100.0  100.0
 main                     Main                    137           0    0.0    0.0    86.7   76.6
  feedWithInputData       Main                    150           1    6.7    0.0     8.3    4.7
   feedWithInputData.func Main                    154      100000    1.7    4.7     1.7    4.7
  writeData               Main                    148           1   30.0   18.8    78.3   71.8
   put                    Main                    155      100000   48.3   53.0    48.3   53.0
  readData                Main                    147           0    0.0    0.1     0.0    0.1
  writeEmptyDataToDisk    Main                    142           0    0.0    0.0     0.0    0.1
   writeData              Main                    143           0    0.0    0.1     0.0    0.1
 CAF:main1                Main                    133           0    0.0    0.0     0.0    0.0
  main                    Main                    136           1    0.0    0.0     0.0    0.0
 CAF:main2                Main                    132           0    0.0    0.0     0.0    0.0
  main                    Main                    139           0    0.0    0.0     0.0    0.0
   writeEmptyDataToDisk   Main                    140           1    0.0    0.0     0.0    0.0
    writeData             Main                    141           1    0.0    0.0     0.0    0.0
 CAF:main7                Main                    131           0    0.0    0.0     0.0    0.0
  main                    Main                    145           0    0.0    0.0     0.0    0.0
   readData               Main                    146           1    0.0    0.0     0.0    0.0
 CAF:dataset1             Main                    123           0    0.0    0.0     5.0    7.8
  dataset1                Main                    151           1    5.0    7.8     5.0    7.8
 CAF:dataset4             Main                    122           0    0.0    0.0     5.0    7.8
  dataset1                Main                    153           0    5.0    7.8     5.0    7.8
 CAF:dataset5             Main                    121           0    0.0    0.0     3.3    7.8
  dataset1                Main                    152           0    3.3    7.8     3.3    7.8
 CAF:main4                Main                    116           0    0.0    0.0     0.0    0.0
  main                    Main                    138           0    0.0    0.0     0.0    0.0
 CAF:main6                Main                    115           0    0.0    0.0     0.0    0.0
  main                    Main                    149           0    0.0    0.0     0.0    0.0
 CAF:main3                Main                    113           0    0.0    0.0     0.0    0.0
  main                    Main                    144           0    0.0    0.0     0.0    0.0
 CAF                      GHC.Conc.Signal         107           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Encoding         103           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Encoding.Iconv   101           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Handle.FD         94           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.FD                86           0    0.0    0.0     0.0    0.0

现在我添加更多数据:

$ ./Main dataset2 myData.dat +RTS -p -sstderr
     343,601,008 bytes allocated in the heap
     175,650,728 bytes copied during GC
      34,113,936 bytes maximum residency (8 sample(s))
         971,896 bytes maximum slop
              78 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0       640 colls,     0 par    0.082s   0.083s     0.0001s    0.0017s
  Gen  1         8 colls,     0 par    0.140s   0.141s     0.0176s    0.0484s

  INIT    time    0.001s  (  0.001s elapsed)
  MUT     time    0.138s  (  0.139s elapsed)
  GC      time    0.221s  (  0.224s elapsed)
  RP      time    0.000s  (  0.000s elapsed)
  PROF    time    0.000s  (  0.000s elapsed)
  EXIT    time    0.006s  (  0.006s elapsed)
  Total   time    0.370s  (  0.370s elapsed)

  %GC     time      59.8%  (60.5% elapsed)

  Alloc rate    2,485,518,518 bytes per MUT second

  Productivity  39.9% of total user, 39.8% of total elapsed

查看新的prof-file:

    Tue Apr 12 18:15 2016 Time and Allocation Profiling Report  (Final)

       Main +RTS -p -sstderr -RTS dataset2 myData.dat

    total time  =        0.14 secs   (139 ticks @ 1000 us, 1 processor)
    total alloc = 213,866,232 bytes  (excludes profiling overheads)

COST CENTRE            MODULE    %time %alloc

put                    Main       41.0   50.9
writeData              Main       25.9   18.0
get                    Main       25.2   16.8
dataset2               Main        4.3   11.2
readData               Main        1.4    0.8
feedWithInputData.func Main        1.4    2.2


                                                                    individual     inherited
COST CENTRE               MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN                      MAIN                     68           0    0.0    0.0   100.0  100.0
 main                     Main                    137           0    0.0    0.0    95.7   88.8
  feedWithInputData       Main                    148           1    0.7    0.0     2.2    2.2
   feedWithInputData.func Main                    152      100000    1.4    2.2     1.4    2.2
  writeData               Main                    145           1   25.9   18.0    66.9   68.9
   put                    Main                    153      200000   41.0   50.9    41.0   50.9
  readData                Main                    141           0    1.4    0.8    26.6   17.6
   get                    Main                    144           0   25.2   16.8    25.2   16.8
 CAF:main1                Main                    133           0    0.0    0.0     0.0    0.0
  main                    Main                    136           1    0.0    0.0     0.0    0.0
 CAF:main7                Main                    131           0    0.0    0.0     0.0    0.0
  main                    Main                    139           0    0.0    0.0     0.0    0.0
   readData               Main                    140           1    0.0    0.0     0.0    0.0
 CAF:dataset2             Main                    126           0    0.0    0.0     0.7    3.7
  dataset2                Main                    149           1    0.7    3.7     0.7    3.7
 CAF:dataset6             Main                    125           0    0.0    0.0     2.2    3.7
  dataset2                Main                    151           0    2.2    3.7     2.2    3.7
 CAF:dataset7             Main                    124           0    0.0    0.0     1.4    3.7
  dataset2                Main                    150           0    1.4    3.7     1.4    3.7
 CAF:$fBinaryDualNo1      Main                    120           0    0.0    0.0     0.0    0.0
  get                     Main                    143           1    0.0    0.0     0.0    0.0
 CAF:main4                Main                    116           0    0.0    0.0     0.0    0.0
  main                    Main                    138           0    0.0    0.0     0.0    0.0
 CAF:main6                Main                    115           0    0.0    0.0     0.0    0.0
  main                    Main                    146           0    0.0    0.0     0.0    0.0
 CAF:main5                Main                    114           0    0.0    0.0     0.0    0.0
  main                    Main                    147           0    0.0    0.0     0.0    0.0
 CAF:main3                Main                    113           0    0.0    0.0     0.0    0.0
  main                    Main                    142           0    0.0    0.0     0.0    0.0
 CAF                      GHC.Conc.Signal         107           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Encoding         103           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Encoding.Iconv   101           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.Handle.FD         94           0    0.0    0.0     0.0    0.0
 CAF                      GHC.IO.FD                86           0    0.0    0.0     0.0    0.0

我添加新数据的次数越多,内存使用量就越高。我的意思是,很清楚,我需要更多的内存来存放更大的数据集。但是这个问题没有更好的解决方案(比如逐渐将数据写回磁盘)。

修改 实际上,困扰我的最重要的是以下观察:

  1. 我是第一次运行程序并将新数据添加到磁盘上的现有(空)文件中。 我磁盘上保存文件的大小为:1.53 MByte。 但是(查看第一个prof-file)程序分配了超过102 MByte。超过50%是由Data.Binary软件包中的put函数分配的。
  2. 我第二次运行程序并将新数据添加到磁盘上的现有(非空)文件中。 我磁盘上保存文件的大小为3.05 MByte。 但是(查看第二个prof-file)程序分配了超过213 MByte。超过66%由put和get功能分配。
  3. =&GT;结论:在第一个示例中,我需要运行程序的内存为102 / 1.53 = 66倍,而不是磁盘上二进制文件的空间。 在第二个例子中,我需要213 / 3.05 =运行程序的内存比我磁盘上二进制文件的空间多69倍。

    问题: 用于序列化的Data.Binary包是如此高效(并且令人敬畏),它可以将所需的内存减少到这样的程度。 类似的问题: 我是否真的需要更多的内存来加载我的程序中的数据而不是磁盘上的二进制文件中的相同数据的空间?

0 个答案:

没有答案