2010-10-16 15 views
100

मैं समझ नहीं क्यों एम 1 जाहिरा तौर पर memoized है, जबकि एम 2 में नहीं है निम्नलिखित:जीएचसी हास्केल में स्वचालित रूप से ज्ञापन कब होता है?

m1  = ((filter odd [1..]) !!) 

m2 n = ((filter odd [1..]) !! n) 

एम 1 10000000 पहली कॉल पर लगभग 1.5 सेकंड लेता है, और कहा कि आगामी कॉल पर का एक अंश (संभवतः यह सूची कैश), जबकि एम 2 10000000 हमेशा एक ही समय लेता है (प्रत्येक कॉल के साथ सूची का पुनर्निर्माण)। कोई विचार क्या चल रहा है? क्या अंगूठे के कोई नियम हैं कि क्या और जब जीएचसी एक समारोह को याद रखेगा? धन्यवाद।

उत्तर

105

जीएचसी कार्यों को याद नहीं करता है।

हालांकि, यह कोड में किसी दिए गए अभिव्यक्ति को प्रति बार एक बार गणना करता है कि इसके आसपास के लैम्ब्डा-अभिव्यक्ति दर्ज की जाती है, या सबसे अधिक बार यह शीर्ष स्तर पर होती है। निर्धारण जहां लैम्ब्डा-अभिव्यक्ति कर रहे हैं एक छोटे से मुश्किल है जब आप अपने उदाहरण की तरह वाक्यात्मक चीनी का उपयोग किया जा सकता है, तो चलो बराबर desugared वाक्य रचना करने के लिए इन कन्वर्ट करते हैं:

m1' = (!!) (filter odd [1..])    -- NB: See below! 
m2' = \n -> (!!) (filter odd [1..]) n 

(नोट: हास्केल 98 रिपोर्ट वास्तव में एक छोड़ दिया ऑपरेटर का वर्णन करता है (a %) तरह अनुभाग \b -> (%) a b के रूप में बराबर है, लेकिन GHC (%) a करने के लिए इसे desugars। ये तकनीकी रूप से अलग हैं, क्योंकि वे द्वारा seq। मुझे लगता है कि मैं इस बारे में GHC Trac टिकट प्रस्तुत हो सकता है प्रतिष्ठित किया जा सकता।)

इस को देखते हुए, आप कर सकते हैं देखें कि m1' में, अभिव्यक्ति filter odd [1..] निहित नहीं है I n किसी भी lambda- अभिव्यक्ति, तो यह केवल आपके प्रोग्राम के प्रति रन के बाद गणना की जाएगी, जबकि m2', filter odd [1..] प्रत्येक बार लैम्ब्डा-अभिव्यक्ति दर्ज की जाएगी, यानी m2' के प्रत्येक कॉल पर गणना की जाएगी। इससे आप जो समय देख रहे हैं उसमें अंतर बताते हैं।


दरअसल, कुछ ऑप्टिमाइज़ेशन विकल्पों के साथ जीएचसी के कुछ संस्करण उपरोक्त वर्णन से अधिक मूल्य साझा करेंगे। यह कुछ स्थितियों में समस्याग्रस्त हो सकता है। उदाहरण के लिए, समारोह

f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x]) 
पर विचार

GHC देख सकते हैं कि y

f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x]) 

इस मामले में करने के लिए x पर निर्भर करते हैं और फिर से लिखने नहीं करता समारोह, नए संस्करण में काफी कम कुशल है, क्योंकि यह होगा स्मृति से लगभग 1 जीबी पढ़ने के लिए जहां y संग्रहीत किया जाता है, जबकि मूल संस्करण निरंतर स्थान पर चलाया जाता है और प्रोसेसर के कैश में फिट होता है। वास्तव में, GHC 6.12.1 के तहत, समारोह f लगभग दो बार के रूप में तेजी से जब बिना अनुकूलन संकलित की तुलना में यह -O2 साथ संकलित किया गया है है।

+0

मूल्यांकन करने की लागत (फिल्टर विषम [1 ..]) अभिव्यक्ति शून्य के करीब है - यह सभी के बाद आलसी सूची है, इसलिए असली लागत (x !! 10000000) एप्लिकेशन में है जब सूची वास्तव में है का मूल्यांकन किया। इसके अलावा, एम 1 और एम 2 दोनों को कम से कम एक परीक्षण के भीतर -O2 और -O1 (मेरे ghc 6.12.3 पर) का मूल्यांकन किया जाता है: (test = m1 10000000 'seq' m1 10000000)। हालांकि कोई फर्क नहीं पड़ता है जब कोई अनुकूलन ध्वज निर्दिष्ट नहीं किया जाता है। और आपके "एफ" के दोनों प्रकारों में ऑप्टिमाइज़ेशन के बावजूद 5356 बाइट्स का अधिकतम निवास है, वैसे (कम कुल आवंटन के साथ -ओ 2 का उपयोग किया जाता है)। –

+1

@ एडका: 'f' की उपरोक्त परिभाषा के साथ, इस परीक्षण कार्यक्रम को आजमाएं:' main = interact $ unlines। (शो। नक्शा एफ पढ़ें)। lines'; '-O2' के साथ या बिना संकलित करें; फिर 'echo 1 | ।/Main'। यदि आप 'मुख्य = प्रिंट (एफ 5)' जैसे परीक्षण लिखते हैं, तो 'y' कचरा इकट्ठा किया जा सकता है क्योंकि इसका उपयोग किया जाता है और दोनों' एफ 'के बीच कोई अंतर नहीं होता है। –

+0

एर, यह निश्चित रूप से 'नक्शा (शो। एफ। पढ़ा जाना चाहिए' होना चाहिए। और अब जब मैंने जीएचसी 6.12.3 डाउनलोड किया है, तो मुझे जीएचसी 6.12.1 के समान परिणाम दिखाई देते हैं। और हां, आप मूल 'एम 1' और' एम 2' के बारे में सही हैं: जीएचसी के संस्करण जो अनुकूलन के साथ इस तरह के भारोत्तोलन को सक्षम करते हैं, 'एम 2' को' एम 1' में बदल देंगे। –

26

एम 1 केवल एक बार गणना की है क्योंकि यह एक लगातार अनुप्रयोगी फार्म है, जबकि एम 2 एक सीएएफ नहीं है, और इसलिए प्रत्येक मूल्यांकन के लिए की जाती है है। monomorphism प्रतिबंध एम 1 पर लागू होता है, लेकिन एम 2 नहीं, क्योंकि एम 2 स्पष्ट रूप से तर्क दिया गया है: http://www.haskell.org/haskellwiki/Constant_applicative_form

+0

स्पष्टीकरण "एम 1 केवल एक बार गणना की जाती है क्योंकि यह एक निरंतर आवेदक प्रपत्र है" मुझे समझ में नहीं आता है। चूंकि संभवतः एम 1 और एम 2 दोनों शीर्ष-स्तरीय चर हैं, मुझे लगता है कि इन _functions_ की गणना केवल एक बार की जाती है, भले ही वे सीएएफ हों या नहीं। अंतर यह है कि सूची '[1 ..] 'किसी प्रोग्राम के निष्पादन के दौरान केवल एक बार गणना की जाती है या फ़ंक्शन के प्रति आवेदन के बाद इसकी गणना की जाती है, लेकिन क्या यह सीएएफ से संबंधित है? –

+1

लिंक किए गए पृष्ठ से: "एक सीएएफ ... या तो ग्राफ के एक टुकड़े में संकलित किया जा सकता है जिसे सभी उपयोगों या कुछ साझा कोड द्वारा साझा किया जाएगा जो पहली बार मूल्यांकन किए जाने वाले कुछ ग्राफ के साथ स्वयं को ओवरराइट कर देगा"। चूंकि 'एम 1' एक सीएएफ है, दूसरा लागू होता है और 'फ़िल्टर विषम [1 ..]' (न केवल '[1 ..]'!) केवल एक बार गणना की जाती है। जीएचसी यह भी ध्यान दे सकता है कि 'एम 2' 'फ़िल्टर विषम [1 ..]' को संदर्भित करता है, और 'एम 1' में इस्तेमाल किए गए उसी थंक के लिए एक लिंक रखता है, लेकिन यह एक बुरा विचार होगा: इससे बड़ी मेमोरी लीक हो सकती है कुछ स्थितियों –

+0

@Alexey: '[1 ..]' और 'फ़िल्टर विषम [1 ..]' के सुधार के लिए धन्यवाद। बाकी के लिए, मैं अभी भी असुविधाजनक हूँ। अगर मुझे गलत नहीं लगता है, तो सीएफ़ केवल तभी प्रासंगिक होता है जब हम तर्क देना चाहते हैं कि एक कंपाइलर _could_ 'फ़िल्टर विषम [1 ..] 'वैश्विक स्तर पर' m2' में (जो 'm1' में उपयोग किए जाने वाले समान थंक भी हो सकता है)। लेकिन पूछताछ की स्थिति में, कंपाइलर ने "ऑप्टिमाइज़ेशन" नहीं किया, और मैं इस सवाल पर अपनी प्रासंगिकता नहीं देख सकता। –

13

वहाँ दो रूपों के बीच एक महत्वपूर्ण अंतर है:

cafs पर GHC विकि देखें। तो एम 2 का प्रकार सामान्य है लेकिन एम 1 विशिष्ट है।प्रकार वे आवंटित कर रहे हैं कर रहे हैं:

m1 :: Int -> Integer 
m2 :: (Integral a) => Int -> a 

अधिकांश हास्केल compilers और दुभाषियों (उन सभी को है कि मैं वास्तव में पता) बहुरूपी संरचनाओं memoize नहीं है, इसलिए एम 2 के आंतरिक सूची हर बार निर्मित है यह, कहा जाता है जहां एम 1 के नहीं है ।

+1

जीएचसीआई में इनके साथ बजाना ऐसा लगता है कि यह लेट-फ्लोटिंग ट्रांसफॉर्मेशन (जीएचसी के अनुकूलन पासों में से एक है जो जीएचसीआई में उपयोग नहीं किया जाता है) के आधार पर भी है। और निश्चित रूप से इन सरल कार्यों को संकलित करते समय, ऑप्टिमाइज़र उन्हें वैसे भी व्यवहार करने में सक्षम बनाता है (कुछ मानदंड परीक्षणों के मुताबिक मैंने अलग मॉड्यूल में कार्यों के साथ और नोइनलाइन प्रागम्स के साथ चिह्नित किया)। संभवतः ऐसा इसलिए है क्योंकि सूची पीढ़ी और अनुक्रमण एक सुपर तंग पाश में फंस जाता है वैसे भी। – mokus

1

मुझे यकीन नहीं है, क्योंकि मैं खुद हास्केल के लिए काफी नया हूं, लेकिन ऐसा प्रतीत होता है कि यह दूसरा फ़ंक्शन पैरामीटर है और पहला वाला नहीं है। फ़ंक्शन की प्रकृति यह है कि, इसका परिणाम इनपुट मान पर निर्भर करता है और कार्यात्मक प्रतिमान में especailly यह केवल इनपुट पर निर्भर करता है। स्पष्ट निहितार्थ यह है कि कोई पैरामीटर वाला फ़ंक्शन हमेशा वही मान देता है, इससे कोई फर्क नहीं पड़ता।

स्पष्ट रूप से जीएचसी कंपाइलर में एक अनुकूलन तंत्र है जो इस तथ्य का उपयोग पूरे कार्यक्रम रनटाइम के लिए केवल एक बार इस तरह के फ़ंक्शन के मूल्य की गणना करने के लिए करता है। यह सुनिश्चित करने के लिए आलसी है, लेकिन फिर भी यह करता है। मैं इसे अपने आप देखा, जब मैं निम्नलिखित समारोह लिखा है:

primes = filter isPrime [2..] 
    where isPrime n = null [factor | factor <- [2..n-1], factor `divides` n] 
     where f `divides` n = (n `mod` f) == 0 

तो यह परीक्षण करने के लिए, मैं GHCi में प्रवेश किया और लिखा है: primes !! 1000। इसमें कुछ सेकंड लग गए, लेकिन आखिरकार मुझे जवाब मिला: 7927। तब मैंने primes !! 1001 कहा और तुरंत जवाब मिला। इसी तरह एक पल में मुझे take 1000 primes का नतीजा मिला, क्योंकि हास्केल को पूरी हज़ार-तत्व सूची की गणना करने के लिए पहले 1001 तत्वों को वापस करना था।

इस प्रकार यदि आप अपना कार्य लिख सकते हैं जैसे कि इसमें कोई पैरामीटर नहीं है, तो आप शायद इसे चाहते हैं। ;)

संबंधित मुद्दे