出于性能方面的原因,我希望将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
范围之外。所以我的问题是: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包中的流融合和调整。 最佳答案
免责声明:这里的所有内容都是实现细节,特定于GHC和发布时相关图书馆的内部表示。
这个响应是事实发生后的几年,但是确实有可能获得一个指向字节数组内容的指针。这是有问题的,因为GC喜欢在堆中移动数据,并且GC堆之外的内容可能会泄漏,这不一定是理想的。 GHC通过以下方式解决了这一问题:newPinnedByteArray# :: Int# -> State# s -> (#State# s, MutableByteArray# s#)
原始字节数组(内部类型定义的C char数组)可以静态固定到地址。 GC保证不移动它们。您可以使用以下函数将字节数组引用转换为指针:byteArrayContents# :: ByteArray# -> Addr#
地址类型构成Ptr和ForeignPtr类型的基础。 Ptr是标有幻像类型的地址,ForeignPtr是该地址加上对GHC内存和IORef终结器的可选引用。
免责声明:仅当您的ByteString是Haskell构建的时,此方法才有效。否则,您将无法获得对字节数组的引用。您不能取消引用任意地址。不要试图将自己的方式强制转换为字节数组。那就是段错误。例子:
{-# 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和pattern匹配中导入构造函数。
data ByteString = PS !(ForeignPtr Word8) !Int !Int
(\(PS foreignPointer offset length) -> foreignPointer)
现在我们需要将 cargo 从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构建的,PlainPtrs包裹在固定的字节数组周围。它们没有终结器。当它们超出范围时,它们就像GC的常规Haskell数据一样。不过,加法器不计算在内。 GHC假定它们指向GC堆之外的内容。如果字节数组本身不在范围之内,那么您将剩下一个悬空的指针。
data PlainPtr = (MutableByteArray# RealWorld)
(\(PlainPtr mutableByteArray#) -> mutableByteArray#)
MutableByteArrays与ByteArrays相同。如果想要真正的零副本构造,请确保将unsafeCoerce#或unsafeFreeze#设置为字节数组。否则,GHC将创建一个副本。
mbarrTobarr :: MutableByteArray# s -> ByteArray#
mbarrTobarr = unsafeCoerce#
现在,您已经准备好将ByteString的原始内容转换为向量。
最好的祝愿,
关于haskell - 有没有希望将ForeignPtr转换为ByteArray#(对于函数:: ByteString-> Vector),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4908880/