Есть ли надежда привести ForeignPtr к ByteArray# (для функции :: ByteString -> Vector)

Из соображений производительности я бы хотел, чтобы ByteString (на данный момент строго) было преобразовано в Vector. Поскольку Vector — это всего лишь ByteArray# под капотом, а ByteString — это ForeignPtr, это может выглядеть примерно так:

caseBStoVector :: ByteString -> Vector a
caseBStoVector (BS fptr off len) =
    withForeignPtr fptr $ \ptr -> do
        let ptr' = plusPtr ptr off
            p = alignPtr ptr' (alignment (undefined :: a))
            barr = ptrToByteArray# p len  -- I want this function, or something similar 
            barr' = ByteArray barr
            alignI = minusPtr p ptr
            size = (len-alignI) `div` sizeOf (undefined :: a)
        return (Vector 0 size barr')

Это, конечно, неправильно. Даже с отсутствующей функцией ptrToByteArray# кажется, что нужно избегать ptr за пределами области withForeignPtr. Итак, мои вопросы:

  1. Этот пост, вероятно, рекламирует мое примитивное понимание ByteArray#, если кто-то может немного рассказать о ByteArray#, его представлении, как им управлять (GCed) и т. д., я был бы признателен.

  2. Тот факт, что ByteArray# живет в куче GCed, а ForeignPtr является внешним, кажется фундаментальной проблемой - все операции доступа разные. Возможно, мне следует переопределить Vector из = ByteArray !Int !Int во что-то с другой косвенностью? Что-то вроде = Location !Int !Int, где data Location = LocBA ByteArray | LocFPtr ForeignPtr и обеспечивают операции упаковки для обоих этих типов? Однако эта косвенность может слишком сильно повредить производительности.

  3. Не сумев соединить эти две вещи вместе, может быть, я смогу просто получить доступ к произвольным типам элементов в ForeignPtr более эффективным способом. Кто-нибудь знает библиотеку, которая обрабатывает ForeignPtr (или ByteString) как массив произвольных типов Storable или Primitive? Это все равно лишило бы меня потокового слияния и настройки из пакета Vector.


person Thomas M. DuBuisson    schedule 05.02.2011    source источник


Ответы (2)


Отказ от ответственности: все здесь является деталями реализации и относится к GHC и внутренним представлениям рассматриваемых библиотек на момент публикации.

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

newPinnedByteArray# :: Int# -> State# s -> (#State# s, MutableByteArray# s#)

Примитивные массивы байтов (массивы C char с внутренним определением типа) могут быть статически привязаны к адресу. GC гарантирует, что они не будут перемещены. Вы можете преобразовать ссылку на bytearray в указатель с помощью этой функции:

byteArrayContents# :: ByteArray# -> Addr#

Тип адреса лежит в основе типов Ptr и ForeignPtr. Ptrs — это адреса, помеченные фантомным типом, а ForeignPtrs — это плюс необязательные ссылки на память GHC и финализаторы IORef.

Отказ от ответственности: это будет только работать, если ваша ByteString была построена на Haskell. В противном случае вы не сможете получить ссылку на bytearray. Вы не можете разыменовать произвольный адрес. Не пытайтесь приводить или навязывать свой путь к массиву байтов; таким образом лежит segfaults. Пример:

{-# LANGUAGE MagicHash, UnboxedTuples #-}

import GHC.IO
import GHC.Prim
import GHC.Types

main :: IO()
main = test

test :: IO ()        -- Create the test array.
test = IO $ \s0 -> case newPinnedByteArray# 8# s0 of {(# s1, mbarr# #) ->
                     -- Write something and read it back as baseline.
                   case writeInt64Array# mbarr# 0# 1# s1 of {s2 ->
                   case readInt64Array# mbarr# 0# s2 of {(# s3, x# #) ->
                     -- Print it. Should match what was written.
                   case unIO (print (I# x#)) s3 of {(# s4, _ #) ->
                     -- Convert bytearray to pointer.
                   case byteArrayContents# (unsafeCoerce# mbarr#) of {addr# ->
                     -- Dereference the pointer.
                   case readInt64OffAddr# addr# 0# s4 of {(# s5, x'# #) ->
                     -- Print what's read. Should match the above.
                   case unIO (print (I# x'#)) s5 of {(# s6, _ #) ->
                     -- Coerce the pointer into an array and try to read.
                   case readInt64Array# (unsafeCoerce# addr#) 0# s6 of {(# s7, y# #) ->
                     -- Haskell is not C. Arrays are not pointers.
                     -- This won't match. It might segfault. At best, it's garbage.
                   case unIO (print (I# y#)) s7 of (# s8, _ #) -> (# s8, () #)}}}}}}}}


Output:
   1
   1
 (some garbage value)

Чтобы получить массив байтов из ByteString, вам нужно импортировать конструктор из Data.ByteString.Internal и сопоставить шаблон.

data ByteString = PS !(ForeignPtr Word8) !Int !Int
(\(PS foreignPointer offset length) -> foreignPointer)

Теперь нам нужно выдрать товар из ForeignPtr. Эта часть полностью зависит от реализации. Для GHC импортируйте из GHC.ForeignPtr.

data ForeignPtr a = ForeignPtr Addr# ForeignPtrContents
(\(ForeignPtr addr# foreignPointerContents) -> foreignPointerContents)

data ForeignPtrContents = PlainForeignPtr !(IORef (Finalizers, [IO ()]))
                        | MallocPtr      (MutableByteArray# RealWorld) !(IORef (Finalizers, [IO ()]))
                        | PlainPtr       (MutableByteArray# RealWorld)

В GHC ByteString создается с помощью PlainPtrs, которые обернуты вокруг закрепленных массивов байтов. Они не несут финализаторов. Им нравятся обычные данные Haskell, когда они выходят за рамки. Однако адреса не в счет. GHC предполагает, что они указывают на вещи за пределами кучи GC. Если сам массив байтов выпадает из области видимости, у вас остается висячий указатель.

data PlainPtr = (MutableByteArray# RealWorld)
(\(PlainPtr mutableByteArray#) -> mutableByteArray#)

MutableByteArrays идентичны ByteArrays. Если вам нужна настоящая конструкция с нулевым копированием, убедитесь, что вы используете unsafeCoerce# или unsafeFreeze# для массива байтов. В противном случае GHC создает дубликат.

mbarrTobarr :: MutableByteArray# s -> ByteArray#
mbarrTobarr = unsafeCoerce#

И теперь у вас есть необработанное содержимое ByteString, готовое для преобразования в вектор.

С наилучшими пожеланиями,

person user2472093    schedule 25.08.2013