Есть ли какая-нибудь надежда лишить ForeignPtr для ByteArray # (для функции:: ByteString → Vector)

По соображениям производительности мне хотелось бы получить нулевое копирование ByteString (строгое, на данный момент) до Vector. Поскольку Vector является просто a 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. Итак, мои вопросы:

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

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

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

Ответ 1

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

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

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

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

byteArrayContents# :: ByteArray# -> Addr#

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

Отказ от ответственности: это будет работать, только если ваш ByteString был построен Haskell. В противном случае вы не можете получить ссылку на bytearray. Вы не можете разыменовывать произвольный адр. Не пытайтесь бросить или принудить свой путь к 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 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 garbage.
                   case unIO (print (I# y#)) s7 of (# s8, _ #) -> (# s8, () #)}}}}}}}}


Output:
   1
   1
 (some garbage value)

Чтобы получить bytearray из 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, которые обернуты вокруг закрепленных байт-массивов. Они не имеют финализаторов. Они являются GC'd, как обычные данные Haskell, когда они выходят за рамки. Однако addrs не учитываются. GHC предполагает, что они указывают на вещи вне кучи GC. Если сам bytearray выходит из области действия, вы остаетесь с висящим указателем.

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

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

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

И теперь у вас есть исходное содержимое ByteString, готовое к превращению в вектор.

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

Ответ 2

Возможно, вы сможете взломать что-то :: ForeignPtr -> Maybe ByteArray#, но вы ничего не можете сделать вообще.

Вы должны посмотреть на модуль Data.Vector.Storable. Он включает функцию unsafeFromForeignPtr :: ForeignPtr a -> Int -> Int -> Vector a. Это похоже на то, что вы хотите.

Существует также вариант Data.Vector.Storable.Mutable.