असुरक्षित DerformIO और accursedUnutterablePerformIO के बीच क्या अंतर है?


13

मैं हास्केल लाइब्रेरी के प्रतिबंधित खंड में भटक रहा था और इन दो विले मंत्रों को पाया:

{- 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#। इस प्रश्न का समुचित उत्तर देने वाले किसी व्यक्ति की प्रतीक्षा है।
लेहिन्स

जवाबों:


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सिंगल आवंटन में फ़्लोट किए जा सकते हैं, पॉइंटर को दो बाइटस्ट्रेस के बीच साझा किया गया है।

यदि आप इनमें से किसी भी कार्य से इनलाइनिंग को हटाते हैं, तो फ्लोटिंग को रोका जा सकेगा, और प्रोग्राम 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, यह है वास्तव में संकलक द्वारा inlined, लेकिन आवंटन कॉल के बाद संकलन के अंत के पास पहले से ही चल करने से रोका गया है।

तो, आपको उस unsafeDupablePerformIOकॉल के पूरी तरह से बिना साइड इफ़ेक्ट के पूर्ण रूप से इनलाइन होने का परफॉरमेंस बेनिफिट मिलता है, जिससे अलग-अलग असुरक्षित कॉल्स में सामान्य एक्सप्रेशंस को एक सिंगल सिंगल कॉल में फ्लोट किया जा सकता है।

हालांकि, सच कहा जाए, तो एक लागत है। जब accursedUnutterablePerformIOसही ढंग से काम करता है, तो यह संभावित रूप से थोड़ा बेहतर प्रदर्शन दे सकता है क्योंकि अनुकूलन के लिए अधिक अवसर हैं यदि m realWorld#कॉल बाद में होने के बजाय पहले से इनलाइन हो सकती है। इसलिए, वास्तविक bytestringपुस्तकालय अभी भी accursedUnutterablePerformIOआंतरिक रूप से बहुत से स्थानों पर उपयोग करता है , विशेष रूप से जहां कोई आवंटन नहीं है (उदाहरण के लिए, headइसका उपयोग बफर के पहले बाइट को झांकने के लिए करता है)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.