在Haskell中生成大量文本的有效方法

时间:2014-12-31 15:33:03

标签: string haskell text

完整的源代码和分析报告位于:https://gist.github.com/anonymous/92334d00859c3db0ba8a

我正在尝试以文本PPM图像文件的形式生成一个大文本文件,我遇到了一些性能问题。我可以使用其他图像格式,但文本生成性能让我感兴趣,因为我有其他类似的情况,我没有选择替代格式的灵活性。

我开始将String连接起来生成我的文本文件,并很快发现它占用了近90%的执行时间。所以,我切换到Data.Text,但发现性能没有显着提高。我最终生成了一个测试文件,试图找出问题,比较两个函数:

ppmS (Matrix mx) = unlines ["P3", show w', show w', "255", pixels]
  where
    w'      = mxSize mx * scale
    pixels  = unlines $ map row [0..w'-1]
    row j   = intercalate " " $ map (pix j) [0..w'-1]
    pix j i = color (mx ! (div i scale, div j scale))

ppmT (Matrix mx) = T.unlines ["P3", T.pack (show w'), T.pack (show w'), "255", pixels]
  where
    w'      = mxSize mx * scale
    pixels  = T.unlines $ map row [0..w'-1]
    row j   = T.intercalate " " $ map (pix j) [0..w'-1]
    pix j i = color (mx ! (div i scale, div j scale))

使用以下命令在探查器中运行它:

ghc -O2 --make -prof -auto-all -caf-all -fforce-recomp test.hs
./test +RTS -p

我看到以下内容:

total time  =        0.60 secs   (597 ticks @ 1000 us, 1 processor)
total alloc = 1,162,898,488 bytes  (excludes profiling overheads)

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

MAIN                     MAIN     96           0    0.0    0.0   100.0  100.0
 main                    Main    193           0   24.1   14.7    24.1   14.7
 CAF:main3               Main    188           0    0.0    0.0    39.9   37.0
  main                   Main    225           0    0.0    0.0    39.9   37.0
   main.ppmFromText      Main    226           0    0.0    0.0    39.9   37.0
    ppmT                 Main    227           0    8.5    9.3    39.9   37.0
     ppmT.row            Main    252           0    0.0    0.0     0.0    0.0
     ppmT.pixels         Main    250           1    8.7    9.3    31.3   27.7
      ppmT.row           Main    251         500   20.6   18.4    22.6   18.4
       ppmT.pix          Main    253      250000    1.8    0.0     2.0    0.0
        color            Main    254      250000    0.2    0.0     0.2    0.0
 CAF:main6               Main    171           0    0.0    0.0    35.8   48.3
  main                   Main    198           0    0.0    0.0    35.8   48.3
   main.ppmFromString    Main    199           0    0.0    0.0    35.8   48.3
    ppmS                 Main    200           0    9.4   14.4    35.8   48.3
     ppmS.pixels         Main    216           1    8.5   14.5    26.5   33.9
      ppmS.row           Main    217         500   13.9   19.4    17.9   19.4
       ppmS.pix          Main    218      250000    3.5    0.0     4.0    0.0
        color            Main    219      250000    0.5    0.0     0.5    0.0

告诉我,TextString版本都占用了大量时间并分配了大量内存。

生成此文本的更好方法是在时间和效率方面提高效率。记忆?

1 个答案:

答案 0 :(得分:3)

更新:事实证明,只需使用ByteStrings代替Text:

import qualified Data.ByteString.Char8 as T
import qualified Data.ByteString as TI

实现了与我最初尝试的Blaze方法相同甚至更好的性能。请参阅最后的更新统计信息表。

原始答案:

使用blaze builder monoid可以获得更好的效果。

以下是适合使用Blaze.ByteString.Builder的算法:

import Blaze.ByteString.Builder
import Blaze.ByteString.Builder.Char8
import Data.Monoid
import Data.List (intersperse)

munlines = mconcat . map ( <> (fromChar '\n') )
mintercalate s xs = mconcat $ intersperse s xs

ppmB (Matrix mx) = munlines [ fromString "P3",
                              fromString (show w'),
                              fromString (show w'),
                              fromString "255",
                              pixels ]
  where
    w'      = mxSize mx * scale
    pixels  = munlines $ map row [0..w'-1]
    row j   = mintercalate (fromString " ") $ map (pix j) [0..w'-1]
    pix j i = fromString $ color (mx ! (div i scale, div j scale))

main = do
  let m = makeMatrix
  let ppmFromString = toLazyByteString $ ppmB m
  LBS.writeFile "output.ppm" ppmFromString

完整资源here

在我的机器上,我得到以下四种版本的RTS统计数据:

             Allocated   Time      %GC
  string     561 MB      0.40 s    56.6 %
  text       601 MB      0.25 s     6.9 %
  blaze       95 MB      0.07 s     3.0 %
  bytestring  91 MB      0.06 s    10.1 %

另一种选择是使用二进制包中的Put monad。