出于性能原因,我希望将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包中调整。
答案 0 :(得分:8)
免责声明:此处的所有内容均为实施细节,具体针对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是用虚线类型标记的地址,ForeignPtrs是GHC内存和IORef终结器的可选引用。
免责声明:如果您的ByteString是Haskell构建的,那么只会工作。否则,您无法获得对bytearray的引用。您不能取消引用任意地址。不要试图强制转换或强制转换为bytearray;那种方式就是段错误。例如:
{-# 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获取bytearray,您需要从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堆之外的东西。如果bytearray本身超出了范围,你就会留下一个悬空指针。
data PlainPtr = (MutableByteArray# RealWorld)
(\(PlainPtr mutableByteArray#) -> mutableByteArray#)
MutableByteArrays与ByteArrays相同。如果您想要真正的零拷贝构造,请确保将unsafeCoerce#或unsafeFreeze#设置为bytearray。否则,GHC会创建副本。
mbarrTobarr :: MutableByteArray# s -> ByteArray#
mbarrTobarr = unsafeCoerce#
现在你已经准备好将ByteString的原始内容变成一个向量。
祝福,
答案 1 :(得分:2)
你可能能够将某些东西:: ForeignPtr -> Maybe ByteArray#
混在一起,但一般来说你无能为力。
您应该查看Data.Vector.Storable
模块。它包含一个函数unsafeFromForeignPtr :: ForeignPtr a -> Int -> Int -> Vector a
。这听起来像你想要的。
还有一个Data.Vector.Storable.Mutable
变体。