अजगर धीमा है। क्यों?

हाल ही में, एक पायथन प्रोग्रामिंग भाषा की बढ़ती लोकप्रियता का निरीक्षण कर सकता है। इसका उपयोग DevOps में, डेटा विश्लेषण में, वेब विकास में, सुरक्षा के क्षेत्र में और अन्य क्षेत्रों में किया जाता है। लेकिन यहाँ गति है ... यहाँ इस भाषा का घमंड करने के लिए कुछ भी नहीं है। सामग्री के लेखक, जिसका अनुवाद आज हम प्रकाशित करते हैं, ने पायथन की सुस्ती के कारणों का पता लगाने और इसे तेज करने के साधन खोजने का फैसला किया।



सामान्य प्रावधान


प्रदर्शन के मामले में जावा, C या C ++ से कैसे संबंधित है? सी # और पायथन की तुलना कैसे करें? इन सवालों के जवाब शोधकर्ता द्वारा विश्लेषण किए गए अनुप्रयोगों के प्रकार पर बहुत अधिक निर्भर करते हैं। कोई सही बेंचमार्क नहीं है, लेकिन विभिन्न भाषाओं में लिखे गए कार्यक्रमों के प्रदर्शन का अध्ययन करना, द कंप्यूटर लैंग्वेज बेंचमार्क गेम एक अच्छा शुरुआती बिंदु हो सकता है।

मैं दस साल से अधिक समय से द कंप्यूटर लैंग्वेज बेंचमार्क गेम का जिक्र कर रहा हूं। पायथन, अन्य भाषाओं की तुलना में, जैसे कि जावा, सी #, गो, जावास्क्रिप्ट, सी ++, सबसे धीमी में से एक है। इसमें JIT संकलन (C #, Java) और AOT संकलन (C #, C ++) का उपयोग करने वाली भाषाएं और साथ ही साथ जावास्क्रिप्ट जैसी भाषाओं की व्याख्या भी शामिल हैं।

यहाँ मैं ध्यान देना चाहूंगा कि जब मैं "पायथन" कहता हूं, तो मेरा मतलब है कि पायथन दुभाषिया - सीपीथॉन का संदर्भ कार्यान्वयन। इस सामग्री में, हम इसके अन्य कार्यान्वयन पर स्पर्श करेंगे। दरअसल, यहां मैं इस सवाल के जवाब को खोजना चाहता हूं कि क्यों तुलनीय समस्याओं को हल करने के लिए पायथन अन्य भाषाओं की तुलना में 2-10 गुना अधिक समय लेता है, और क्या यह तेजी से किया जा सकता है।

यहाँ कुछ बुनियादी सिद्धांत समझाने की कोशिश कर रहे हैं कि पायथन धीमा क्यों है:

  • इसका कारण GIL (ग्लोबल इंटरप्रेटर लॉक, ग्लोबल इंटरप्रेटर लॉक) है।
  • कारण यह है कि पायथन एक संकलित भाषा के बजाय एक व्याख्या है।
  • इसका कारण डायनामिक टाइपिंग है।

हम इन विचारों का विश्लेषण करेंगे और पायथन अनुप्रयोगों के प्रदर्शन पर सबसे अधिक प्रभाव डालने वाले प्रश्न के उत्तर को खोजने की कोशिश करेंगे।

जीआईएल


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

उदाहरण के लिए, मेरा क्रोम ब्राउज़र, जिस समय मैं यह लिखता हूं, उसमें 44 खुले धागे होते हैं। यह ध्यान में रखा जाना चाहिए कि धाराओं के साथ काम करने के लिए सिस्टम की संरचना और एपीआई पॉज़िक्स-आधारित ऑपरेटिंग सिस्टम (मैक ओएस, लिनक्स) और ऑपरेटिंग सिस्टम के विंडोज परिवार में भिन्न होती है। ऑपरेटिंग सिस्टम भी थ्रेड्स की योजना बनाता है।

यदि आप पहले बहु-थ्रेडेड प्रोग्रामिंग से नहीं मिले हैं, तो अब आपको तथाकथित ताले (ताले) से परिचित होने की आवश्यकता है। तालों का अर्थ यह है कि वे आपको सिस्टम के ऐसे व्यवहार को सुनिश्चित करने की अनुमति देते हैं, जब बहु-थ्रेडेड वातावरण में, उदाहरण के लिए, स्मृति में एक निश्चित चर को बदलते समय, कई थ्रेड्स एक ही मेमोरी क्षेत्र (पढ़ने या बदलने के लिए) तक पहुंच प्राप्त नहीं कर सकते हैं।

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

सबसे दिलचस्प हिस्सा तब शुरू होता है जब कई धागे एक ही चर को साझा करते हैं, और यहां मुख्य समस्या यह है कि सीपीथॉन संदर्भ गणना कैसे करता है। यह वह जगह है जहां "वैश्विक दुभाषिया ताला" की कार्रवाई दिखाई देती है, जो थ्रेड्स के निष्पादन को सावधानीपूर्वक नियंत्रित करती है।

एक दुभाषिया एक बार में केवल एक ऑपरेशन कर सकता है, भले ही कार्यक्रम में कितने धागे हों।

Affect GIL पायथन अनुप्रयोगों के प्रदर्शन को कैसे प्रभावित करता है?


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

यदि, एक पायथन इंटरप्रेटर प्रक्रिया के ढांचे के भीतर, मल्टीथ्रेडिंग तंत्रों का उपयोग करके समानांतर डेटा प्रोसेसिंग को लागू करना आवश्यक है, और उपयोग की जाने वाली धाराएँ I / O सबसिस्टम का गहन रूप से उपयोग करेंगी (उदाहरण के लिए, यदि वे नेटवर्क या डिस्क के साथ काम करते हैं), तो इसके परिणामों का निरीक्षण करना संभव होगा। कैसे जीआईएल धागे का प्रबंधन करता है। यहां दो थ्रेड्स का उपयोग करने के मामले में यह दिखता है, गहन लोडिंग प्रक्रियाएं।


GIL विज़ुअलाइज़ेशन ( यहाँ से लिया गया )

यदि आपके पास एक वेब एप्लिकेशन है (उदाहरण के लिए, Django फ्रेमवर्क के आधार पर) और आप WSGI का उपयोग करते हैं, तो वेब एप्लिकेशन के प्रत्येक अनुरोध को एक अलग पायथन इंटरप्रेटर प्रक्रिया द्वारा सेवा दी जाएगी, अर्थात हमारे पास केवल 1 अनुरोध लॉक है। चूंकि पायथन इंटरप्रेटर धीरे-धीरे शुरू होता है, कुछ डब्ल्यूएसजीआई कार्यान्वयन में एक तथाकथित "डेमन मोड" होता है, जिसका उपयोग करते समय काम की स्थिति में दुभाषिया प्रक्रियाओं को बनाए रखा जाता है, जो सिस्टम को तेजी से अनुरोधों को पूरा करने की अनुमति देता है।

▍ अन्य पायथन दुभाषियों का व्यवहार कैसा है?


PyPy में GIL है, यह आमतौर पर CPython की तुलना में 3 गुना अधिक तेज है।

Jython में GIL नहीं है, क्योंकि Jython में Python थ्रेड्स को Java थ्रेड्स के रूप में दर्शाया गया है। ऐसे धागे जेवीएम की मेमोरी प्रबंधन क्षमताओं का उपयोग करते हैं।

▍ जावास्क्रिप्ट में प्रवाह नियंत्रण कैसे आयोजित किया जाता है?


यदि हम जावास्क्रिप्ट के बारे में बात करते हैं, तो, सबसे पहले, यह ध्यान दिया जाना चाहिए कि सभी जेएस इंजन मार्क-एंड-स्वीप कचरा संग्रह एल्गोरिदम का उपयोग करते हैं। जैसा कि पहले ही उल्लेख किया गया है, GIL का उपयोग करने का मुख्य कारण CPython में उपयोग किया जाने वाला मेमोरी प्रबंधन एल्गोरिथम है।

जावास्क्रिप्ट में एक जीआईएल नहीं है, हालांकि, जेएस एक एकल-थ्रेडेड भाषा है, इसलिए, इस तरह के तंत्र की आवश्यकता नहीं है। समानांतर कोड निष्पादन के बजाय, जावास्क्रिप्ट एक घटना लूप, वादों और कॉलबैक के आधार पर अतुल्यकालिक प्रोग्रामिंग तकनीकों का उपयोग करता है। पायथन में asyncio मॉड्यूल द्वारा प्रदान किया गया कुछ समान है।

अजगर - भाषा की व्याख्या की


मैंने अक्सर सुना है कि पायथन का खराब प्रदर्शन इस तथ्य के कारण है कि यह एक व्याख्या की गई भाषा है। ऐसे बयान इस बात पर आधारित हैं कि सीपीथॉन वास्तव में कैसे काम करता है। यदि, टर्मिनल में, आप python myscript.py तरह एक कमांड दर्ज करते हैं, तो CPython क्रियाओं का एक लंबा क्रम शुरू करेगा, जिसमें स्क्रिप्ट कोड पढ़ना, लेक्सिकल विश्लेषण, पार्सिंग, संकलन, व्याख्या करना और निष्पादित करना शामिल है। यदि आप विवरण में रुचि रखते हैं, तो इस सामग्री पर एक नज़र डालें।

हमारे लिए, इस प्रक्रिया पर विचार करते समय, यह विशेष रूप से महत्वपूर्ण है कि यहां, संकलन चरण में, एक .pyc फ़ाइल बनाई गई है, और .pyc का एक क्रम __pycache__/ निर्देशिका में फ़ाइल को लिखा गया है, जिसका उपयोग पायथन 3 और पायथन दोनों में किया जाता है। 2।

यह न केवल हमारे द्वारा लिखी गई लिपियों पर लागू होता है, बल्कि तीसरे पक्ष के मॉड्यूल सहित आयातित कोड पर भी लागू होता है।

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

नतीजतन, जावा और सी # दोनों में कुछ "मध्यवर्ती भाषा" का उपयोग किया जाता है और समान तंत्र मौजूद हैं। फिर, पायथन जावा और सी # की तुलना में बहुत खराब बेंचमार्क क्यों दिखाता है, अगर इन सभी भाषाओं में वर्चुअल मशीन और किसी तरह के बायटेकोड का उपयोग होता है? सबसे पहले, इस तथ्य के कारण कि जेआईटी संकलन का उपयोग .NET और जावा में किया जाता है।

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

अपने आप से, जेआईटी का उपयोग करने से कोड के निष्पादन में तेजी नहीं आती है, क्योंकि बाइट कोड के कुछ टुकड़े निष्पादन में आते हैं, जैसे कि पायथन में। हालांकि, JIT निष्पादन के दौरान आपको कोड अनुकूलन करने की अनुमति देता है। एक अच्छा JIT ऑप्टिमाइज़र एप्लिकेशन के सबसे अधिक लोड किए गए हिस्सों की पहचान करने में सक्षम है (आवेदन का यह हिस्सा "हॉट स्पॉट" कहा जाता है) और संबंधित कोड के टुकड़ों को अनुकूलित करें, जो पहले उपयोग किए गए थे, उनकी तुलना में अनुकूलित और अधिक उत्पादक विकल्पों की जगह।

इसका मतलब यह है कि जब कोई निश्चित एप्लिकेशन बार-बार कुछ कार्य करता है, तो इस तरह के अनुकूलन से ऐसे कार्यों के निष्पादन में तेजी आ सकती है। इसके अलावा, ध्यान रखें कि जावा और C # दृढ़ता से टाइप की जाने वाली भाषाएं हैं, इसलिए ऑप्टिमाइज़र कोड के बारे में अधिक धारणा बना सकता है जो प्रदर्शन को बेहतर बनाने में मदद कर सकता है।

PyPy में JIT कंपाइलर है, और जैसा कि पहले ही उल्लेख किया गया है, यह Python दुभाषिया कार्यान्वयन CPython की तुलना में बहुत तेज़ है। विभिन्न पायथन दुभाषियों की तुलना करने पर जानकारी इस लेख में मिल सकती है।

▍ सीपीथॉन जेआईटी संकलक का उपयोग क्यों नहीं कर रहा है?


जेआईटी कंपाइलर्स के नुकसान भी हैं। उनमें से एक लॉन्च समय है। CPython पहले से ही अपेक्षाकृत धीरे-धीरे शुरू होता है, और PyPy, CPython से 2-3 गुना धीमा है। जेवीएम का लंबा समय भी एक ज्ञात तथ्य है। CLR .NET सिस्टम बूट के दौरान शुरू करके इस समस्या को दरकिनार कर देता है, लेकिन यह ध्यान दिया जाना चाहिए कि CLR और ऑपरेटिंग सिस्टम दोनों CLR को एक ही कंपनी द्वारा विकसित किए जाते हैं।

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

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

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

नतीजतन, हम कह सकते हैं कि यदि आप एक प्रोग्राम लिखने के लिए पायथन का उपयोग कर रहे हैं जिसका प्रदर्शन JIT कंपाइलर का उपयोग करते समय बेहतर हो सकता है, तो PyPy दुभाषिया का उपयोग करें।

पायथन एक गतिशील रूप से टाइप की जाने वाली भाषा है


वैधानिक रूप से टाइप की गई भाषाओं में, चर घोषित करते समय, आपको उनके प्रकार निर्दिष्ट करने होंगे। इन भाषाओं में C, C ++, Java, C #, Go नोट किए जा सकते हैं।

गतिशील रूप से टाइप की गई भाषाओं में, डेटा प्रकार की अवधारणा का एक ही अर्थ है, लेकिन एक चर का प्रकार गतिशील है।

 a = 1 a = "foo" 

इस सरलतम उदाहरण में, पायथन पहले पहला चर बनाता a , फिर दूसरा उसी प्रकार के नाम के साथ str , और उस मेमोरी को मुक्त करता है जिसे पहले चर a को आवंटित किया गया था।

ऐसा प्रतीत हो सकता है कि डायनेमिक टाइपिंग वाली भाषाओं में लेखन स्थिर टाइपिंग वाली भाषाओं की तुलना में अधिक सुविधाजनक और सरल है, हालांकि, ऐसी भाषाएं किसी के व्हाट्सएप पर नहीं बनाई गईं। उनके विकास के दौरान, कंप्यूटर सिस्टम की विशेषताओं को ध्यान में रखा गया है। सब कुछ जो प्रोग्राम टेक्स्ट में लिखा गया है, अंत में, प्रोसेसर निर्देशों के लिए नीचे आता है। इसका अर्थ है कि प्रोग्राम द्वारा उपयोग किया जाने वाला डेटा, उदाहरण के लिए, ऑब्जेक्ट या अन्य प्रकार के डेटा के रूप में, निम्न-स्तरीय संरचनाओं में भी परिवर्तित किया जाता है।

पायथन ऐसे परिवर्तनों को स्वचालित रूप से करता है, प्रोग्रामर इन प्रक्रियाओं को नहीं देखता है, और उसे ऐसे परिवर्तनों की देखभाल करने की आवश्यकता नहीं है।

यह घोषित करते समय एक चर के प्रकार को निर्दिष्ट नहीं करना भाषा की एक विशेषता नहीं है जो पायथन को धीमा बनाता है। भाषा वास्तुकला लगभग कुछ भी गतिशील बनाने के लिए संभव बनाता है। उदाहरण के लिए, रन टाइम पर, आप ऑब्जेक्ट मेथड्स को बदल सकते हैं। फिर से, कार्यक्रम के निष्पादन के दौरान, आप "मंकी पैच" तकनीक का उपयोग कर सकते हैं जैसा कि निम्न-स्तरीय सिस्टम कॉल पर लागू किया जाता है। पायथन में, लगभग कुछ भी संभव है।

यह अजगर वास्तुकला है जो अनुकूलन को बहुत कठिन बनाता है।

इस विचार को स्पष्ट करने के लिए, मैं MacOS पर ट्रेसिंग सिस्टम कॉल ट्रेस करने के लिए एक टूल का उपयोग करने जा रहा हूं।

तैयार CPython वितरण में कोई DTrace सपोर्ट मैकेनिज्म नहीं हैं, इसलिए CPython को उपयुक्त सेटिंग्स के साथ recompiled करना होगा। यहां संस्करण 3.6.6 का उपयोग किया जाता है। इसलिए, हम क्रियाओं के निम्नलिखित अनुक्रम का उपयोग करते हैं:

 wget https://github.com/python/cpython/archive/v3.6.6.zip unzip v3.6.6.zip cd v3.6.6 ./configure --with-dtrace make 

अब, python.exe का उपयोग python.exe , आप कोड का पता लगाने के लिए DTRace का उपयोग कर सकते हैं। यहाँ पायथन के साथ DTrace का उपयोग करने के बारे में पढ़ें। और यहां आप DTrace का उपयोग करके पायथन कार्यक्रमों के विभिन्न प्रदर्शन संकेतकों को मापने के लिए स्क्रिप्ट पा सकते हैं। उनमें कॉलिंग फ़ंक्शंस, प्रोग्राम्स का रनटाइम, प्रोसेसर के उपयोग का समय, सिस्टम कॉल के बारे में जानकारी, और इसी तरह के पैरामीटर हैं। यहाँ dtrace कमांड का उपयोग कैसे करें:

 sudo dtrace -s toolkit/<tracer>.d -c '../cpython/python.exe script.py' 

और यहां बताया गया है कि py_callflow ट्रेस py_callflow एप्लिकेशन में फ़ंक्शन कॉल कैसे दिखाती है।


अनुरेखण का उपयोग करते हुए

अब आइए इस प्रश्न का उत्तर दें कि क्या डायनेमिक टाइपिंग पाइथन प्रदर्शन को प्रभावित करता है। इस पर कुछ विचार हैं:

  • टाइप चेकिंग और रूपांतरण भारी संचालन हैं। हर बार एक चर को एक्सेस किया जाता है, पढ़ा या लिखा जाता है, एक प्रकार की जांच की जाती है।
  • इस तरह के लचीलेपन के साथ एक भाषा को अनुकूलित करना मुश्किल है। पायथन की तुलना में अन्य भाषाएं इतनी तेज हैं कि वे लचीलेपन और प्रदर्शन के बीच चयन करके समझौता करते हैं।
  • साइथन परियोजना पायथन और स्थिर टाइपिंग को जोड़ती है, जो उदाहरण के लिए, जैसा कि इस लेख में दिखाया गया है, नियमित पायथन पर 84 गुना प्रदर्शन सुधार की ओर जाता है। अगर आपको गति की आवश्यकता है तो इस परियोजना को देखें।

परिणाम


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

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

प्रिय पाठकों! आप गरीब पायथन प्रदर्शन के मुद्दों को कैसे हल करते हैं?

Source: https://habr.com/ru/post/hi418823/


All Articles