unsafeDupablePerformIO และ accursedUnutterablePerformIO แตกต่างกันอย่างไร


13

ฉันหลงทางในส่วนที่ จำกัด ของ Haskell Library และพบคาถาสองอันนี้:

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

พิจารณาไลบรารี bytestring ที่ง่ายขึ้น คุณอาจมีประเภทสตริงไบต์ประกอบด้วยความยาวและบัฟเฟอร์ที่จัดสรรของไบต์:

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 1และsingleton 2สามารถลอยออกไปสู่การจัดสรรครั้งเดียวโดยมีตัวชี้ที่ใช้ร่วมกันระหว่างการทดสอบทั้งสอง

หากคุณต้องลบอินไลน์ออกจากฟังก์ชั่นใด ๆ เหล่านี้การลอยจะถูกป้องกันและโปรแกรมจะพิมพ์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#แอพลิเคชันที่มีการเรียกฟังก์ชั่นที่ไม่ inlined myRunRW# m = m realWorld#ไป นี่เป็นรหัสน้อยที่สุดที่หากไม่มีการ inlined สามารถป้องกันไม่ให้มีการยกสายการจัดสรร

หลังจากการเปลี่ยนแปลงนี้โปรแกรมจะพิมพ์Falseตามที่คาดไว้

นี่คือทั้งหมดที่เปลี่ยนจากinlinePerformIO(AKA accursedUnutterablePerformIO) เพื่อunsafeDupablePerformIOไม่ มันเปลี่ยนการเรียกฟังก์ชั่นนั้นm realWorld#จากการแสดงออกที่ inline เป็น noninlined เทียบเท่า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#โทรสามารถถูก inline ก่อนหน้านี้แทนภายหลัง ดังนั้นbytestringไลบรารีที่แท้จริงยังคงใช้accursedUnutterablePerformIOภายในในหลาย ๆ ที่โดยเฉพาะอย่างยิ่งที่ไม่มีการจัดสรรเกิดขึ้น (เช่นheadใช้เพื่อมองหาไบต์แรกของบัฟเฟอร์)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.