พิจารณาไลบรารี 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#
ถ้าผมจะคิดว่ามันอาจจะต้องทำอะไรกับอินไลน์และลอยออกมาจาก หวังว่าจะมีคนให้คำตอบที่ถูกต้องสำหรับคำถามนี้