gpt4 book ai didi

haskell - unsafeDupablePerformIO 和 accursedUnutterablePerformIO 有什么区别?

转载 作者:行者123 更新时间:2023-12-02 01:34:46 25 4
gpt4 key购买 nike

我在 Haskell 图书馆的限制区闲逛,发现了这两个邪恶的咒语:

{- System.IO.Unsafe -}
unsafeDupablePerformIO :: IO a -> a
unsafeDupablePerformIO (IO m) = case runRW# m of (# _, a #) -> a

{- Data.ByteString.Internal -}
accursedUnutterablePerformIO :: IO a -> a
accursedUnutterablePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
实际差异似乎只是在 runRW# 之间。和 ($ realWorld#) , 然而。我对他们在做什么有一些基本的了解,但我没有得到使用一个而不是另一个的真正后果。有人可以解释一下有什么区别吗?

最佳答案

考虑一个简化的字节串库。您可能有一个字节字符串类型,由长度和分配的字节缓冲区组成:

data BS = BS !Int !(ForeignPtr Word8)

要创建字节串,您通常需要使用 IO 操作:
create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
p <- mallocForeignPtrBytes n
withForeignPtr p $ f
return $ BS n p

但是,在 IO monad 中工作并不是那么方便,因此您可能会尝试做一些不安全的 IO:
unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f

鉴于库中的大量内联,最好内联不安全的 IO,以获得最佳性能:
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r

但是,在您添加一个用于生成单例字节串的便利函数之后:
singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)

您可能会惊讶地发现以下程序会打印 True :
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

import GHC.IO
import GHC.Prim
import Foreign

data BS = BS !Int !(ForeignPtr Word8)

create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
p <- mallocForeignPtrBytes n
withForeignPtr p $ f
return $ BS n p

unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f

myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r

singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)

main :: IO ()
main = do
let BS _ p = singleton 1
BS _ q = singleton 2
print $ p == q

如果您期望两个不同的单例使用两个不同的缓冲区,这是一个问题。

这里的问题是广泛的内联意味着两个 mallocForeignPtrBytes 1来电 singleton 1singleton 2可以 float 到单个分配中,指针在两个字节串之间共享。

如果您要从这些函数中的任何一个中删除内联,则将阻止 float ,并且程序将打印 False正如预期的那样。或者,您可以对 myUnsafePerformIO 进行以下更改:
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case myRunRW# m of (# _, r #) -> r

myRunRW# :: forall (r :: RuntimeRep) (o :: TYPE r).
(State# RealWorld -> o) -> o
{-# NOINLINE myRunRW# #-}
myRunRW# m = m realWorld#

替换内联 m realWorld#具有对 myRunRW# m = m realWorld# 的非内联函数调用的应用程序.这是最小的代码块,如果没有内联,可以防止分配调用被解除。

更改后,程序将打印 False正如预期的那样。

这就是从 inlinePerformIO 切换的所有内容(又名 accursedUnutterablePerformIO)到 unsafeDupablePerformIO做。它改变了函数调用 m realWorld#从内联表达式到等效的非内联 runRW# m = m realWorld# :
unsafeDupablePerformIO  :: IO a -> a
unsafeDupablePerformIO (IO m) = case runRW# m of (# _, a #) -> a

runRW# :: forall (r :: RuntimeRep) (o :: TYPE r).
(State# RealWorld -> o) -> o
{-# NOINLINE runRW# #-}
runRW# m = m realWorld#

除了,内置的 runRW#是魔法。即使它被标记为 NOINLINE ,它实际上是由编译器内联的,但是在分配调用已经被阻止 float 之后接近编译结束时。

因此,您可以获得 unsafeDupablePerformIO 的性能优势。 call 完全内联,没有该内联的不良副作用,允许将不同不安全调用中的公共(public)表达式 float 到一个公共(public)的单个调用。

不过,说实话,是有代价的。当 accursedUnutterablePerformIO正常工作,它可能会提供稍微更好的性能,因为如果 m realWorld# 有更多的优化机会call 可以更早地而不是更晚地内联。所以,实际的 bytestring图书馆仍然使用 accursedUnutterablePerformIO在很多地方内部,特别是没有分配的地方(例如, head 使用它来查看缓冲区的第一个字节)。

关于haskell - unsafeDupablePerformIO 和 accursedUnutterablePerformIO 有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61021205/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com