2014-12-31 3 views
3

полный исходный код и профилирование отчет здесь: https://gist.github.com/anonymous/92334d00859c3db0ba8aЭффективный способ для создания больших объемов текста в Haskell

Я пытаюсь генерации большого текстового файла в виде файла изображения текста РРМ, и я я сталкиваюсь с некоторыми проблемами производительности. Я мог бы использовать другой формат изображения, но производительность генерации текста заинтересовала меня, поскольку у меня есть другие подобные ситуации, когда у меня нет гибкости выбора альтернативного формата.

Я начал с конкатенирования String s вместе для создания текстового файла и быстро выяснил, что он занял почти 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 

, который сообщает мне, что и версии Text, и String занимают значительное время и выделяют значительную память.

Что такое лучший способ генерировать этот текст, который более эффективен во времени & памяти?

+0

Я не предлагаю, чтобы это проблема с производительностью, но вы можете избежать «T.pack» и «show» с помощью Text.Show (http://hackage.haskell.org/package/text-show) –

ответ

3

Update: Как есть получается, просто используя байтовых строк вместо текста с:

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

достигает точно такой же или даже более высокую производительность, чем подход Blaze я сначала попробовал. См. Обновленную таблицу статистики в конце.

Оригинальный ответ:

можно достичь лучших результатов, используя взрывной строитель моноид.

Вот ваш алгоритм адаптирован для использования 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.

На моей машине я получаю следующие статистики РТС за четыре версии:

   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 монады из бинарного пакета.

+0

Итак, в основном, используйте ByteStrings, а не текст? – Ana

+0

@ Ана, на самом деле это не текст Юникода, а формат, основанный на ASCII, поэтому имеет смысл пропустить сложность «Текста». Другая большая вещь, я думаю, заключается в использовании построителя с ленивыми (chunked) bytestrings, а не просто на основе массива представления, что намного хуже для конкатенации и, возможно, также для использования в памяти.Альтернативой может быть использование пакетов bytestring, связанных с трубами или кабелепроводом. – dfeuer

+0

Да, просто использование ByteStrings вместо Text даст вам аналогичную производительность. С другой стороны, вы также можете использовать подход Blaze с текстом - вы получите кодировку UTF8 с кодировкой конкатенации. Если вы действительно хотите, чтобы результат был значением Text, я думаю, вам нужно выбрать другой алгоритм. Преимущество подхода Blaze заключается в том, что вам не нужно менять алгоритм. – ErikR

Смежные вопросы