Как я могу преобразовать (StorableArray (Int, Int) Word8) в ленивую строку ByteString? - PullRequest
6 голосов
/ 27 июня 2010

Я пытаюсь загрузить файл PNG, получить несжатые байты RGBA, а затем отправить их в пакеты gzip или zlib.

Пакет pngload возвращает данные изображения в виде (StorableArray (Int, Int) Word8)), а пакеты сжатия принимают ленивые строки ByteStrings.Поэтому я пытаюсь построить функцию (StorableArray (Int, Int) Word8 -> ByteString).

До сих пор я пробовал следующее:

import qualified Codec.Image.PNG as PNG
import Control.Monad (mapM)
import Data.Array.Storable (withStorableArray)
import qualified Data.ByteString.Lazy as LB (ByteString, pack, take)
import Data.Word (Word8)
import Foreign (Ptr, peekByteOff)

main = do
    -- Load PNG into "image"...
    bytes <- withStorableArray 
        (PNG.imageData image)
        (bytesFromPointer lengthOfImageData)

bytesFromPointer :: Int -> Ptr Word8 -> IO LB.ByteString
bytesFromPointer count pointer = LB.pack $ 
    mapM (peekByteOff pointer) [0..(count-1)]

Это вызывает стекизрасходовать память, так ясно, что я делаю что-то очень неправильно.Есть еще кое-что, что я мог бы попробовать с Ptr и ForeignPtr, но там много «небезопасных» функций.

Любая помощь здесь будет принята;Я довольно тупой.

Ответы [ 2 ]

7 голосов
/ 27 июня 2010

Как правило, упаковка и распаковка - плохая идея для производительности.Если у вас есть Ptr и длина в байтах, вы можете сгенерировать строгую тестовую строку двумя различными способами:

Примерно так:

import qualified Codec.Image.PNG as PNG
import Control.Monad
import Data.Array.Storable (withStorableArray)

import Codec.Compression.GZip

import qualified Data.ByteString.Lazy   as L
import qualified Data.ByteString.Unsafe as S

import Data.Word
import Foreign

-- Pack a Ptr Word8 as a strict bytestring, then box it to a lazy one, very
-- efficiently
bytesFromPointer :: Int -> Ptr Word8 -> IO L.ByteString
bytesFromPointer n ptr = do
    s <- S.unsafePackCStringLen (castPtr ptr, n)
    return $! L.fromChunks [s]

-- Dummies, since they were not provided 
image = undefined
lengthOfImageData = 10^3

-- Load a PNG, and compress it, writing it back to disk
main = do
    bytes <- withStorableArray
        (PNG.imageData image)
        (bytesFromPointer lengthOfImageData)
    L.writeFile "foo" . compress $ bytes

Я использую версию O (1), которая просто перепаковывает Ptr из StorableArray.Вы можете сначала скопировать его через "packCStringLen".

3 голосов
/ 27 июня 2010

Проблема с вашим "bytesFromPointer" заключается в том, что вы берете упакованное представление StorableArray из pngload и хотите преобразовать его в другое упакованное представление ByteString, проходя через промежуточный список. Иногда лень означает, что промежуточный список не будет создан в памяти, но здесь это не так.

Функция «mapM» является первым нарушителем. Если вы развернете mapM (peekByteOff pointer) [0..(count-1)], вы получите

el0 <- peekByteOff pointer 0
el1 <- peekByteOff pointer 1
el2 <- peekByteOff pointer 2
...
eln <- peekByteOff pointer (count-1)
return [el0,el1,el2,...eln]

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

Даже если список был составлен лениво, как отмечает Дон Стюарт, функция «pack» все равно испортит вашу производительность. Проблема с «пакетом» состоит в том, что ему нужно знать, сколько элементов в списке, чтобы выделить правильный объем памяти. Чтобы найти длину списка, программа должна пройти его до конца. Из-за необходимости вычисления длины список должен быть полностью загружен, прежде чем он может быть упакован в строку байтов.

Я считаю "mapM" вместе с "pack" запахом кода. Иногда вы можете заменить «mapM» на «mapM_», но в этом случае лучше использовать функции создания байтовых строк, например, "PackCStringLen".

...