unsafeDupablePerformIO와 accursedUnutterablePerformIO의 차이점은 무엇입니까?


13

나는 Haskell Library의 Restricted Section에서 방황하고 다음 두 가지 사악한 주문을 발견했습니다.

{- 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

두 개의 다른 싱글 톤이 두 개의 다른 버퍼를 사용할 것으로 예상되는 경우 문제가됩니다.

무엇이 잘못 여기거야 것은 그 두하는 광범위한 인라인 수단 mallocForeignPtrBytes 1의 통화 singleton 1및이 singleton 2두 bytestrings 사이에 공유 포인터로, 하나의 할당 속으로 떠내려 할 수 있습니다.

이러한 함수 중 하나에서 인라인을 제거하면 플로팅이 방지되고 프로그램이 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.