พิจารณาไลบรารี 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ใช้เพื่อมองหาไบต์แรกของบัฟเฟอร์)
unsafeDupablePerformIOปลอดภัยกว่าด้วยเหตุผลบางอย่างrunRW#ถ้าผมจะคิดว่ามันอาจจะต้องทำอะไรกับอินไลน์และลอยออกมาจาก หวังว่าจะมีคนให้คำตอบที่ถูกต้องสำหรับคำถามนี้