unsafeDupablePerformIOとaccursedUnutterablePerformIOの違いは何ですか?


13

私はハスケル図書館の制限付きセクションをさまよっていて、次の2つの卑劣な呪文を見つけました。

{- 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#)しかし、。私は彼らが何をしているのかについていくつかの基本的な考えを持っていますが、私はそれらを重ねて使用することの本当の結果は得ていません。誰かが違いを教えてくれませんか?


3
unsafeDupablePerformIO何らかの理由で安全です。私が推測しなければならなかった場合、それはおそらくインライン化とフローティングから何かをしなければならないでしょうrunRW#。この質問に適切な答えを与える誰かを楽しみにしています。
lehins

回答:


11

単純化されたバイト文字列ライブラリを考えてみましょう。あなたは長さとバイトの割り当てられたバッファで構成されるバイト文字列型を持っているかもしれません:

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モナドで作業するのはそれほど便利ではないので、少し安全でない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

これは、2つの異なるシングルトンが2つの異なるバッファを使用すると予想する場合に問題になります。

何ここで間違って起こっているのは、2回のことを大規模なインライン化手段ということでmallocForeignPtrBytes 1中のコールsingleton 1とは、singleton 22バイト文字列の間で共有ポインタで、1回の割当てに出て浮上させることができます。

これらの関数のいずれかからインライン展開を削除すると、浮動が防止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(AKA 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ていますが、実際にはコンパイラーによってインライン化されていますが、割り当ての呼び出しが既にフロートされないようにした後、コンパイルの終わり近くにあります。

したがって、unsafeDupablePerformIOインライン化の望ましくない副作用なしに呼び出しを完全にインライン化することで、さまざまな安全でない呼び出しの一般的な式を共通の単一の呼び出しにフロートできるというパフォーマンス上の利点が得られます。

正直なところ、コストがかかります。がaccursedUnutterablePerformIO正しく機能する場合、m realWorld#呼び出しがインライン化されるのが遅くなるよりも早い場合、最適化の機会が増えるため、パフォーマンスが若干向上する可能性があります。そのため、実際のbytestringライブラリはaccursedUnutterablePerformIO、多くの場所、特に割り当てが行われていない場所で内部的に使用されます(たとえば、headそれを使用してバッファーの最初のバイトを確認します)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.