단순화 된 바이트 스트링 라이브러리를 고려하십시오. 길이와 할당 된 바이트 버퍼로 구성된 바이트 문자열 유형이있을 수 있습니다.
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
버퍼의 첫 번째 바이트를 엿보기 위해 사용)
unsafeDupablePerformIO
어떤 이유로 든 더 안전합니다. 내가 추측해야한다면 아마도 인라인과 플로팅으로 무언가를해야 할 것입니다runRW#
. 이 질문에 대한 적절한 답변을 제공 할 사람을 기대합니다.