Как я могу преобразовать (StorableArray (Интервал, Интервал) Word8) в ленивый ByteString?

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

pngload пакет возвращает данные изображения как (StorableArray (Интервал, Интервал) Word8), и пакеты сжатия берут ленивый ByteStrings. Поэтому я пытаюсь создать (StorableArray (Интервал, Интервал) 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, но существует много "небезопасных" функций там.

Любая справка здесь ценилась бы; я справедливо озадачен.

6
задан Don Stewart 20 April 2011 в 04:37
поделиться

2 ответа

Как правило, упаковка и распаковка - плохая идея для производительности. Если у вас есть 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".

7
ответ дан 10 December 2019 в 00:32
поделиться

Проблема с вашим "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]

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

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

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

3
ответ дан 10 December 2019 в 00:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: