2011-09-24 15 views
59

मुझे हाल ही में F# for Python programmers के बारे में एक प्रस्तुति मिली, और इसे देखने के बाद, मैंने "चींटी पहेली" के समाधान को लागू करने का निर्णय लिया।एफ # बनाम ओकैम: स्टैक ओवरफ़्लो

एक चींटी है जो एक प्लानर ग्रिड पर घूम सकती है। चींटी एक स्थान बाईं ओर, दाएं, ऊपर या नीचे एक स्थानांतरित कर सकती है। यही है, सेल (एक्स, वाई) से चींटी कोशिकाओं (x + 1, y), (x-1, y), (x, y + 1), और (x, y-1) पर जा सकती है। अंक जहां एक्स और वाई निर्देशांक के अंकों की संख्या 25 से अधिक है, चींटी के लिए पहुंच योग्य नहीं हैं। उदाहरण के लिए, बिंदु (5 9, 7 9) पहुंच योग्य नहीं है क्योंकि 5 + 9 + 7 + 9 = 30, जो 25 से अधिक है। सवाल यह है कि अगर यह शुरू होता है तो एंटी एक्सेस कितने अंक (1000, 1000) हो सकता है, सहित (1000, 1000) खुद?

मैं OCaml first के 30 लाइनों में मेरी समाधान लागू, और इसे बाहर की कोशिश की:

$ ocamlopt -unsafe -rectypes -inline 1000 -o puzzle ant.ml 
$ time ./puzzle 
Points: 148848 

real 0m0.143s 
user 0m0.127s 
sys  0m0.013s 

साफ, मेरे परिणाम leonardo's implementation, in D and C++ की तरह ही है। लियोनार्डो के सी ++ कार्यान्वयन की तुलना में, ओकैमल संस्करण सी ++ की तुलना में लगभग 2 गुना धीमा चलता है। जो ठीक है, दिया गया है कि लियोनार्डो ने रिकर्सन को हटाने के लिए कतार का उपयोग किया था।

मैं तो translated the code to F# ... और यहाँ मैं क्या मिला है: ...

[email protected] /g/Tmp/ant.fsharp 
$ /g/Program\ Files/FSharp-2.0.0.0/bin/fsc.exe ant.fs 
Microsoft (R) F# 2.0 Compiler build 2.0.0.0 
Copyright (c) Microsoft Corporation. All Rights Reserved. 

[email protected] /g/Tmp/ant.fsharp 
$ ./ant.exe 

Process is terminated due to StackOverflowException. 
Quit 

[email protected] /g/Tmp/ant.fsharp 
$ /g/Program\ Files/Microsoft\ F#/v4.0/Fsc.exe ant.fs 
Microsoft (R) F# 2.0 Compiler build 4.0.30319.1 
Copyright (c) Microsoft Corporation. All Rights Reserved. 

[email protected] /g/Tmp/ant.fsharp 
$ ./ant.exe 

Process is terminated due to StackOverflowException 

ढेर अतिप्रवाह मैं एफ # के दोनों संस्करणों मेरी मशीन में ... जिज्ञासा से बाहर के साथ, मैं तो ले लिया उत्पन्न बाइनरी (ant.exe) और आर्क लिनक्स/मोनो के तहत इसे चलाने:

$ mono -V | head -1 
Mono JIT compiler version 2.10.5 (tarball Fri Sep 9 06:34:36 UTC 2011) 

$ time mono ./ant.exe 
Points: 148848 

real 1m24.298s 
user 0m0.567s 
sys  0m0.027s 

हैरानी की बात है, यह मोनो 2.10.5 (यानी कोई ढेर अतिप्रवाह) के अधीन ही कार्य - लेकिन यह 84 सेकंड, यानी की तुलना में 587 गुना धीमी लेता है ओकैमल - ओह।

तो इस कार्यक्रम के ...

  • रन OCaml
  • के तहत ठीक बिल्कुल नेट के तहत काम नहीं करता है/एफ #
  • काम करता है, लेकिन के तहत मोनो/एफ # बहुत धीमी है,।

क्यों?

संपादित करें: Weirdness जारी है - लेकिन केवल ArchLinux/मोनो के तहत उपयोग करना "--optimize + --checked-" बनाता समस्या गायब हो जाते हैं,; विंडोज एक्सपी और विंडोज 7/64 बिट के तहत, बाइनरी स्टैक ओवरफ्लो का अनुकूलित संस्करण भी।

अंतिम EDIT: मैंने जवाब स्वयं पाया - नीचे देखें।

+0

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

+0

कोड पेस्टबिन के लिंक में है। – ttsiodras

+0

लेकिन कोई सवाल नहीं है, जो स्टैक ओवरफ्लो का पूरा बिंदु है। मैं इसे बंद करने के लिए मतदान कर रहा हूं। – talonmies

उत्तर

69

कार्यकारी सारांश:

  • मैं एक एल्गोरिथ्म का एक सरल कार्यान्वयन लिखा था ... कि पूंछ पुनरावर्ती नहीं था।
  • मैंने इसे लिनक्स के तहत ओकैमल के साथ संकलित किया।
  • यह ठीक काम किया, और 0.14 सेकंड में समाप्त हुआ।

यह समय # पोर्ट के लिए पोर्ट था।

  • मैंने कोड (प्रत्यक्ष अनुवाद) को F # में अनुवादित किया।
  • मैंने विंडोज के तहत संकलित किया, और इसे चलाया - मुझे एक स्टैक ओवरफ़्लो मिला।
  • मैंने लिनक्स के तहत बाइनरी ली और इसे मोनो के तहत चलाया।
  • यह काम किया, लेकिन बहुत धीमी गति से चला (84 सेकंड)।

फिर मैंने स्टैक ओवरफ़्लो पर पोस्ट किया - लेकिन कुछ लोगों ने प्रश्न (श्वास) को बंद करने का फैसला किया।

  • मैं --optimize + --checked-
  • लिनक्स के तहत द्विआधारी अभी भी Windows के तहत overflowed ढेर ...
  • ... लेकिन ठीक से चलाने के लिए (और 0.5 सेकंड में समाप्त हो गया)/मोनो के साथ संकलन की कोशिश की ।

यह स्टैक आकार की जांच करने का समय था: विंडोज़ के तहत, another SO post pointed out that it is set by default to 1MB। लिनक्स के तहत, "uname -s" और a compilation of a test program स्पष्ट रूप से दिखाया गया है कि यह 8 एमबी है।

यह समझाया गया कि कार्यक्रम लिनक्स के तहत क्यों काम करता है और विंडोज के तहत नहीं (कार्यक्रम 1 एमबी से अधिक का उपयोग किया जाता है)। यह समझाया नहीं गया कि अनुकूलित संस्करण मोनो के तहत गैर-अनुकूलित एक की तुलना में इतना बेहतर क्यों चला जाता है: 0.5 सेकंड बनाम 84 सेकंड (भले ही - ऑप्टिमाइज़ + डिफ़ॉल्ट रूप से सेट किया गया हो, फिर भी "विशेषज्ञ एफ #" के साथ कीथ द्वारा टिप्पणी देखें निकालने)। शायद मोनो के कचरा कलेक्टर के साथ करना है, जिसे किसी भी तरह से पहले संस्करण द्वारा चरम सीमा तक पहुंचाया गया था।

लिनक्स/ओकैमल और लिनक्स/मोनो/एफ # निष्पादन समय (0.14 बनाम 0.5) के बीच का अंतर यह है कि मैंने इसे मापने के सरल तरीके के कारण: "समय ./binary ..." स्टार्टअप समय को भी मापता है, जो मोनो/.NET के लिए महत्वपूर्ण है (अच्छी तरह से, इस साधारण छोटी समस्या के लिए महत्वपूर्ण)।

वैसे भी, इसे एक बार और सभी के लिए हल करने के लिए, wrote a tail-recursive version - जहां फ़ंक्शन के अंत में रिकर्सिव कॉल लूप में परिवर्तित हो जाता है (और इसलिए, कोई स्टैक उपयोग आवश्यक नहीं है - कम से कम सिद्धांत में)।

नया संस्करण विंडोज के तहत भी ठीक है, और 0.5 सेकंड में समाप्त हुआ।

तो, कहानी का नैतिक:

  • अपने ढेर उपयोग के खबरदार, खासकर यदि आप इसके बारे में बहुत का उपयोग करें और Windows के तहत चलाते हैं। अपनी बाइनरी को बड़े ढेर आकारों में सेट करने के लिए EDITBIN with the /STACK option का उपयोग करें, या बेहतर अभी तक, अपना कोड इस तरह से लिखें कि बहुत अधिक स्टैक का उपयोग करने पर निर्भर न हो।
  • ओकैम एफ # की तुलना में पूंछ-रिकर्सन उन्मूलन में बेहतर हो सकता है - या यह कचरा कलेक्टर इस विशेष समस्या पर बेहतर काम कर रहा है।
  • के बारे में ... असभ्य लोगों को अपने स्टैक ओवरफ़्लो सवालों को बंद निराशा नहीं करते हैं, अच्छे लोग अंत में उन्हें प्रतिक्रिया करेगा - अगर सवाल वास्तव में अच्छा :-)

पी.एस. हैं डॉ। जॉन हैरोप से कुछ अतिरिक्त इनपुट:

... आप बस भाग्यशाली थे कि ओकैमल भी बहती नहीं थी। आपने पहले से ही पहचाना है कि वास्तविक स्टैक आकार प्लेटफ़ॉर्म के बीच भिन्न होते हैं। एक ही मुद्दे का एक और पहलू यह है कि अलग-अलग भाषा कार्यान्वयन अलग-अलग दरों पर स्टैक स्पेस खाते हैं और गहरे ढेर की उपस्थिति में विशेष प्रदर्शन होते हैं। ओकैमल, मोनो और।नेट सभी अलग-अलग डेटा प्रस्तुतियों और जीसी एल्गोरिदम का उपयोग करते हैं जो इन परिणामों को प्रभावित करते हैं ... (ए) ओकैम पॉइंटर्स, को कॉम्पैक्ट स्टैक फ्रेम देने के लिए टैग किए गए पूर्णांक का उपयोग करता है, और स्टैक पर पॉइंटर्स की तलाश में सब कुछ पार करेगा। टैगिंग अनिवार्य रूप से पर्याप्त जानकारी के लिए ओकैमल रन टाइम के लिए ढेर को पार करने में सक्षम होने के लिए बताती है (बी) मोनो स्टैक्स पर रूढ़िवादी रूप से स्टैक्स पर शब्दों का इलाज करता है: यदि एक पॉइंटर के रूप में, एक शब्द को एक ढेर-आवंटित बिंदु पर इंगित करेगा तब ब्लॉक को उस ब्लॉक को पहुंचने योग्य माना जाता है। (सी) मुझे .NET के एल्गोरिदम नहीं पता है, लेकिन अगर मैंने स्टैक को तेजी से खाया तो यह आश्चर्यचकित नहीं होगा और अभी भी स्टैक पर हर शब्द को पार कर गया है (यह निश्चित रूप से जीसी से पैथोलॉजिकल प्रदर्शन को पीड़ित करता है यदि एक असंबंधित थ्रेड में है गहरी ढेर!) ... इसके अलावा, आपके ढेर-आवंटित टुपल्स का उपयोग करने का मतलब है कि आप नर्सरी पीढ़ी (जैसे जीन 0) को भरने और जल्दी से भरने के लिए जीसी को उन गहरे ढेरों को पार करने के कारण ...

8

मुझे उत्तर सारांशित करने का प्रयास करें।

वहाँ 3 अंक किए जाने के लिए कर रहे हैं:

  • समस्या: ढेर अतिप्रवाह एक पुनरावर्ती समारोह पर होता है
  • यह केवल खिड़कियों के तहत होता है: लिनक्स पर, समस्याओं के आकार की जांच के लिए, यह
  • काम करता है एक ही (या समान) OCaml में कोड काम करता है
  • अनुकूलन + संकलक झंडा, समस्याओं के आकार की जांच के लिए, काम करता है

यह बहुत आम है कि एक स्टैक ओवरफ़्लो अपवाद एक रिकर्सिव वाल का परिणाम है। यदि कॉल पूंछ की स्थिति में है, तो संकलक इसे पहचान सकता है और tailcall अनुकूलन लागू कर सकता है, इसलिए रिकर्सिव कॉल स्टैक स्पेस नहीं लेगा। Tailcall अनुकूलन एफ # में हो सकता है, सीआरएल में, या दोनों में:

CLR पूंछ अनुकूलन 1

एफ # प्रत्यावर्तन (अधिक सामान्य) 2

एफ # पूंछ कॉल 3

सही व्याख्या "विंडोज़ पर विफल रहता है, लिनक्स में नहीं", जैसा कि अन्य ने कहा है, दो ओएस पर डिफ़ॉल्ट आरक्षित स्टैक स्पेस है। या बेहतर, आरक्षित स्टैक स्पेस दो ओएस के तहत कंपाइलर्स द्वारा उपयोग किया जाता है। डिफ़ॉल्ट रूप से, वीसी ++ केवल 1 एमबी स्टैक स्पेस को सुरक्षित रखता है। सीएलआर (संभवतः) वीसी ++ के साथ संकलित है, इसलिए इसमें यह सीमा है। संकलित समय पर संरक्षित स्टैक स्पेस बढ़ाया जा सकता है, लेकिन मुझे यकीन नहीं है कि इसे संकलित निष्पादन योग्य पर संशोधित किया जा सकता है या नहीं।

संपादित करें: है कि यह किया जा सकता है पता चला है (इस ब्लॉग पोस्ट http://www.bluebytesoftware.com/blog/2006/07/04/ModifyingStackReserveAndCommitSizesOnExistingBinaries.aspx देखें) मैं यह सुझा नहीं होगा, लेकिन कम से कम यह संभव है चरम स्थितियों में।

ओकैम संस्करण काम कर सकता है क्योंकि यह लिनक्स के तहत चलाया गया था। हालांकि, विंडोज के तहत ओकैम संस्करण भी परीक्षण करना दिलचस्प होगा। मुझे पता है कि ओकैमल कंपाइलर एफ # की तुलना में पूंछ-कॉल अनुकूलन पर अधिक आक्रामक है .. क्या यह आपके मूल कोड से पूंछ-पुन: प्रयोज्य फ़ंक्शन भी निकाल सकता है?

"--optimize +" के बारे में मेरा अनुमान है कि यह अभी भी कोड को दोबारा शुरू कर देगा, इसलिए यह अभी भी विंडोज के तहत असफल हो जाएगा, लेकिन निष्पादन योग्य तेज़ी से चलकर समस्या को कम करेगा।

अंत में, निश्चित समाधान पूंछ रिकर्सन का उपयोग करना है (कोड को फिर से लिखना या आक्रामक संकलक अनुकूलन पर वास्तविकता से); रिकर्सिव फ़ंक्शंस के साथ स्टैक ओवरफ़्लो समस्या से बचने का यह एक अच्छा तरीका है।

+0

"ओकैमल कंपाइलर एफ # की तुलना में पूंछ-कॉल अनुकूलन पर अधिक आक्रामक है। ज़रुरी नहीं। ओकैमल और एफ # दोनों गारंटी है कि पूंछ की स्थिति में सभी कॉल समाप्त हो गए हैं (उपयुक्त परिस्थितियों में)।हालांकि, ओकैमल शायद .NET या मोनो की तुलना में बहुत छोटे स्टैक फ्रेम का उपयोग कर रहा है। –

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