TL; DR धीमा लूप ऐरे 'आउट-ऑफ-बाउंड्स' तक पहुँचने के कारण है, जो या तो इंजन को कम या कोई अनुकूलन के साथ फ़ंक्शन को फिर से शुरू करने के लिए मजबूर करता है या इसके साथ शुरू करने के लिए इनमें से किसी भी अनुकूलन के साथ फ़ंक्शन को संकलित नहीं करता है ( यदि (JIT-) कम्पाइलर ने पहले संकलन 'संस्करण' से पहले इस स्थिति का पता लगाया / संदेह किया, तो नीचे क्यों पढ़ें;
किसी को सिर्फ
यह कहना है (पूरी तरह से चकित किसी ने पहले से ही नहीं किया है):
एक समय हुआ करता था जब ओपी का स्निपेट एक शुरुआती प्रोग्रामिंग किताब में एक वास्तविक तथ्य का उदाहरण होगा, जिसे जावास्क्रिप्ट में 'सरणियों' को रेखांकित करने या जोर देने के उद्देश्य से शुरू किया जाता है। 0 पर, 1 नहीं, और जैसे कि एक सामान्य 'शुरुआती गलती' के उदाहरण के रूप में उपयोग किया जाता है (क्या आपको प्यार नहीं है कि मैंने वाक्यांश 'प्रोग्रामिंग त्रुटि' से कैसे बचा था
;)
):
आउट-ऑफ-बाउंड्स ऐरे एक्सेस ।
उदाहरण 1: 0-आधारित इंडेक्सिंग (हमेशा ES262 में) का उपयोग करके 5 तत्वों के
एक Dense Array
(सन्निहित के बीच कोई अंतराल नहीं है) और वास्तव में प्रत्येक सूचकांक में एक तत्व होने के नाते।
var arr_five_char=['a', 'b', 'c', 'd', 'e']; // arr_five_char.length === 5
// indexes are: 0 , 1 , 2 , 3 , 4 // there is NO index number 5
इस प्रकार हम वास्तव में <
बनाम <=
(या 'एक अतिरिक्त पुनरावृत्ति') के बीच के प्रदर्शन के अंतर के बारे में बात नहीं कर रहे हैं , लेकिन हम बात कर रहे हैं:
'सही स्निपेट (बी) गलत स्निपेट (ए) से अधिक तेजी से क्यों चलता है?'
इसका उत्तर 2-गुना है (हालांकि ES262 भाषा कार्यान्वयनकर्ता के दृष्टिकोण से दोनों अनुकूलन के रूप हैं):
- डेटा-प्रतिनिधित्व: मेमोरी में आंतरिक रूप से एरे को कैसे दर्शाया / संग्रहीत किया जाए (ऑब्जेक्ट, हैशमैप, 'वास्तविक' संख्यात्मक सरणी, आदि)
- फ़ंक्शनल मशीन-कोड: इन 'एरे' को एक्सेस / हैंडल (पढ़ने / संशोधित) करने वाले कोड को कैसे संकलित किया जाए
आइटम 1 पर्याप्त रूप से (और सही ढंग से IMHO) स्वीकृत उत्तर द्वारा समझाया गया है , लेकिन यह केवल आइटम 2 पर 2 शब्द ('कोड') खर्च करता है : संकलन ।
अधिक सटीक: JIT- संकलन और इससे भी महत्वपूर्ण बात JIT- RE -Compilation!
भाषा विनिर्देश मूल रूप से एल्गोरिदम के एक सेट का एक विवरण है ('परिभाषित अंतिम परिणाम प्राप्त करने के लिए प्रदर्शन करने के लिए कदम')। जो, जैसा कि यह बताता है कि किसी भाषा का वर्णन करने का एक बहुत ही सुंदर तरीका है। और यह वास्तविक विधि को छोड़ देता है जो कि एक इंजन कार्यान्वयनकर्ताओं के लिए खुले निर्दिष्ट परिणामों को प्राप्त करने के लिए उपयोग करता है, जिससे परिभाषित परिणामों का उत्पादन करने के लिए अधिक कुशल तरीकों के साथ आने का पर्याप्त अवसर मिलता है। एक कल्पना अनुरूप इंजन किसी भी परिभाषित इनपुट के लिए कल्पना अनुरूप परिणाम देना चाहिए।
अब, जावास्क्रिप्ट कोड / पुस्तकालयों / उपयोग में वृद्धि के साथ, और याद रखना कि कितने संसाधन (समय / स्मृति / आदि) एक 'वास्तविक' संकलक का उपयोग करता है, यह स्पष्ट है कि हम वेब पेज पर आने वाले उपयोगकर्ताओं को लंबे समय तक इंतजार नहीं कर सकते (और उनकी आवश्यकता है) उपलब्ध है कि कई संसाधनों के लिए)।
निम्नलिखित सरल कार्य की कल्पना करें:
function sum(arr){
var r=0, i=0;
for(;i<arr.length;) r+=arr[i++];
return r;
}
पूरी तरह से स्पष्ट है, है ना? किसी भी अतिरिक्त स्पष्टीकरण की आवश्यकता नहीं है, है ना? वापसी-प्रकार है Number
, है ना?
खैर .. नहीं, नहीं और नहीं ... यह इस बात पर निर्भर करता है कि आप किस नामांकित फंक्शन पैरामीटर को पास करते हैं arr
...
sum('abcde'); // String('0abcde')
sum([1,2,3]); // Number(6)
sum([1,,3]); // Number(NaN)
sum(['1',,3]); // String('01undefined3')
sum([1,,'3']); // String('NaN3')
sum([1,2,{valueOf:function(){return this.val}, val:6}]); // Number(9)
var val=5; sum([1,2,{valueOf:function(){return val}}]); // Number(8)
समस्या देखें? फिर विचार करें कि यह केवल बड़े पैमाने पर संभावित क्रमांकन को मुश्किल से समाप्त कर रहा है ... हमें यह भी नहीं पता है कि फ़ंक्शन RETURN को किस प्रकार से किया जाता है जब तक कि हम नहीं किया जाता है ...
अब इसी फ़ंक्शन की कल्पना करें- कोड वास्तव में विभिन्न प्रकारों या इनपुट की विविधता पर भी उपयोग किया जा रहा है, दोनों पूरी तरह से शाब्दिक (स्रोत कोड में) वर्णित हैं और गतिशील रूप से प्रोग्राम 'एरेज़' उत्पन्न करते हैं।
इस प्रकार, यदि आप sum
केवल JCE ONCE फ़ंक्शन को संकलित करने के लिए थे , तो एकमात्र तरीका जो हमेशा किसी भी और सभी प्रकार के इनपुट के लिए कल्पना-परिभाषित परिणाम लौटाता है, जाहिर है, केवल सभी कल्पना-निर्धारित मुख्य और उप चरणों का प्रदर्शन करके, विशिष्ट अनुरूप परिणामों की गारंटी दे सकता है (एक अनाम प्री-y2k ब्राउज़र की तरह)। कोई अनुकूलन (क्योंकि कोई धारणा नहीं) और मृत धीमी व्याख्या वाली स्क्रिप्टिंग भाषा बनी हुई है।
JIT-Compilation (जस्ट इन टाइम के रूप में JIT) वर्तमान लोकप्रिय समाधान है।
तो, आप यह क्या करता है, रिटर्न और स्वीकार करता है के बारे में मान्यताओं का उपयोग करके फ़ंक्शन को संकलित करना शुरू करते हैं।
यदि आप फ़ंक्शन गैर-कल्पना अनुरूप परिणाम (जैसे कि यह अप्रत्याशित इनपुट प्राप्त करता है) लौटना शुरू कर सकते हैं, तो यह पता लगाने के लिए जितना संभव हो सके चेक के साथ आएं। फिर, पिछले संकलित परिणाम को टॉस करें और कुछ अधिक विस्तृत करने के लिए recompile, तय करें कि आपके पास पहले से मौजूद आंशिक परिणाम के साथ क्या करना है (क्या यह विश्वसनीय होने के लिए वैध है या फिर से सुनिश्चित होने के लिए गणना करें), कार्यक्रम में वापस टाई। पुनः प्रयास करें। अंततः कल्पना के रूप में स्टेप वाइज स्क्रिप्ट-व्याख्या पर वापस गिर रहा है।
इस सब में समय लगता है!
सभी ब्राउज़र अपने इंजनों पर काम करते हैं, प्रत्येक और प्रत्येक उप-संस्करण के लिए आप चीजों को सुधार और फिर से देखेंगे। स्ट्रिंग्स इतिहास में कुछ बिंदु पर थे, वास्तव में अपरिवर्तनीय स्ट्रिंग्स (इसलिए array.join स्ट्रिंग कॉन्सेन्टेशन की तुलना में तेज़ था), अब हम रस्सियों (या समान) का उपयोग करते हैं जो समस्या को कम करते हैं। दोनों कल्पना-अनुरूप परिणाम लौटाते हैं और यही मायने रखता है!
लंबी कहानी छोटी: सिर्फ इसलिए कि जावास्क्रिप्ट की भाषा के शब्दार्थों को अक्सर हमारी पीठ मिल जाती है (जैसे ओपी के उदाहरण में इस मूक बग के साथ) का मतलब यह नहीं है कि 'बेवकूफ' गलतियों से कंपाइलर के तेजी से मशीन-कोड को थूकने की हमारी संभावना बढ़ जाती है। यह मानता है कि हमने 'आमतौर पर' सही निर्देश लिखे हैं: वर्तमान मंत्र हम 'उपयोगकर्ताओं' (प्रोग्रामिंग भाषा का) होना चाहिए: संकलक की मदद करें, हम जो चाहते हैं उसका वर्णन करें, सामान्य मुहावरों का पक्ष लें (मूल समझ से asm.js पर संकेत लें) क्या ब्राउज़र अनुकूलन करने की कोशिश कर सकते हैं और क्यों)।
इस वजह से, प्रदर्शन के बारे में बात करना महत्वपूर्ण है, लेकिन यह भी एक खान-क्षेत्र है (और कहा मेरा-क्षेत्र के कारण मैं वास्तव में कुछ प्रासंगिक सामग्री की ओर इशारा करते हुए (और उद्धृत करते हुए) समाप्त करना चाहता हूं:
गैर-ऑब्जेक्ट ऑब्जेक्ट गुणों और सीमा सरणी तत्वों से बाहर undefined
एक अपवाद बढ़ाने के बजाय मान लौटाता है। ये गतिशील विशेषताएं जावास्क्रिप्ट में प्रोग्रामिंग को सुविधाजनक बनाती हैं, लेकिन वे जावास्क्रिप्ट को कुशल मशीन कोड में संकलित करना भी मुश्किल बनाते हैं।
...
प्रभावी JIT अनुकूलन के लिए एक महत्वपूर्ण आधार यह है कि प्रोग्रामर एक व्यवस्थित तरीके से जावास्क्रिप्ट की गतिशील सुविधाओं का उपयोग करते हैं। उदाहरण के लिए, जेआईटी संकलक इस तथ्य का फायदा उठाते हैं कि ऑब्जेक्ट गुण अक्सर किसी विशिष्ट क्रम में किसी दिए गए प्रकार के ऑब्जेक्ट में जोड़े जाते हैं या सीमा सरणी पहुंच से बाहर शायद ही कभी होते हैं। JIT संकलक रनटाइम पर कुशल मशीन कोड उत्पन्न करने के लिए इन नियमितताओं का फायदा उठाते हैं। यदि कोई कोड ब्लॉक मान्यताओं को संतुष्ट करता है, तो जावास्क्रिप्ट इंजन कुशल, उत्पन्न मशीन कोड निष्पादित करता है। अन्यथा, इंजन को धीमा कोड या प्रोग्राम की व्याख्या करने के लिए वापस गिरना चाहिए।
स्रोत:
"JITProf: पिनपॉइंटिंग JIT-unfriendly JavaScript कोड"
बर्कले प्रकाशन, 2014, लिआंग गोंग, माइकल प्रडेल, कौशिक सेन द्वारा।
http://software-lab.org/publications/jitprof_tr_aug3_2014.pdf
ASM.JS (बाउंड अरेंज एक्सेस को पसंद नहीं करता है):
आगे-समय संकलन
क्योंकि asm.js जावास्क्रिप्ट का एक सबसे बड़ा उपसमूह है, यह विनिर्देश केवल सत्यापन तर्क को परिभाषित करता है- निष्पादन शब्दार्थ केवल जावास्क्रिप्ट का है। हालांकि, मान्य asm.js आगे-से-समय (एओटी) संकलन के लिए उत्तरदायी है। इसके अलावा, AOT कंपाइलर द्वारा उत्पन्न कोड काफी कुशल हो सकता है, जिसमें विशेषता हो सकती है:
- पूर्णांक और फ्लोटिंग-पॉइंट नंबरों का अनबॉक्सड प्रतिनिधित्व;
- रनटाइम प्रकार की जांच की अनुपस्थिति;
- कचरा संग्रह की अनुपस्थिति; तथा
- कुशल हीप लोड और स्टोर (कार्यान्वयन की रणनीति प्लेटफ़ॉर्म से भिन्न होती है)।
कोड जो मान्य करने में विफल रहता है, उसे पारंपरिक साधनों, जैसे, व्याख्या और / या सिर्फ-समय (JIT) संकलन द्वारा निष्पादन में वापस आना चाहिए।
http://asmjs.org/spec/latest/
और अंत में https://blogs.windows.com/msedgedev/2015/05/07/bringing-asm-js-to-chakra-microsoft-edge/
क्या सीमा को हटाते समय इंजन के आंतरिक प्रदर्शन में सुधार के बारे में एक छोटी सी जानकारी थी। चेक (केवल लूप के बाहर सीमा-चेक उठाते समय पहले से ही 40% का सुधार था)।
संपादित करें:
ध्यान दें कि कई स्रोत JIT-Recompilation के विभिन्न स्तरों के बारे में व्याख्या करने के लिए नीचे बात करते हैं।
ओपी के स्निपेट के बारे में उपरोक्त जानकारी के आधार पर सैद्धांतिक उदाहरण :
- IsPrimeDiv अदृश्य को कॉल करें
- संकलित सामान्य मान्यताओं का उपयोग करPrimeDiv अदृश्य है (जैसे सीमा से बाहर नहीं)
- काम करो
- BAM, अचानक सरणी सीमा से बाहर (दाईं ओर) पहुंचता है।
- बकवास, इंजन का कहना है, चलो recompile है जो अलग-अलग (कम) मान्यताओं का उपयोग करते हुए PivimeDiv अदृश्य है, और यह उदाहरण इंजन यह पता लगाने की कोशिश नहीं करता है कि क्या यह वर्तमान आंशिक परिणाम का पुन: उपयोग कर सकता है, इसलिए
- धीमे फ़ंक्शन का उपयोग करके सभी काम को पुन: निष्पादित करें (उम्मीद है कि यह खत्म हो जाए, अन्यथा दोहराएं और इस बार कोड की व्याख्या करें)।
- वापसी का परिणाम
इसलिए समय तब था:
पहला रन (अंत में विफल) + प्रत्येक पुनरावृत्ति आदि के लिए धीमे मशीन-कोड का उपयोग करके सभी काम फिर से करना .. इस सैद्धांतिक उदाहरण में स्पष्ट रूप से> 2 गुना अधिक समय लगता है !
संपादित करें 2: (अस्वीकरण: नीचे दिए गए तथ्यों पर आधारित अनुमान)
जितना अधिक मैं इसके बारे में सोचता हूं, उतना ही मुझे लगता है कि यह जवाब वास्तव में गलत स्निपेट (या स्निपेट बी पर प्रदर्शन-बोनस) पर इस 'जुर्माना' के लिए अधिक प्रमुख कारण बता सकता है। , इस पर निर्भर करता है कि आप इसे कैसे सोचते हैं), ठीक है कि मैं इसे (स्निपेट ए) प्रोग्रामिंग त्रुटि क्यों कह रहा हूं:
यह बहुत आकर्षक है कि this.primes
यह मान लें कि एक 'घने सरणी' शुद्ध संख्यात्मक है जो या तो था
- स्रोत-कोड में हार्ड-कोडेड शाब्दिक ('वास्तविक' सरणी बनने के लिए ज्ञात उत्कृष्ट उम्मीदवार के रूप में सब कुछ संकलन-समय से पहले ही संकलक के लिए जाना जाता है ) या
- सबसे अधिक संभावना एक संख्यात्मक कार्य
new Array(/*size value*/)
क्रम में आरोही क्रम में एक पूर्व-आकार ( ) को भरने के लिए एक संख्यात्मक कार्य का उपयोग करके उत्पन्न (एक और लंबे समय से ज्ञात उम्मीदवार 'वास्तविक' सरणी) बनने के लिए है।
हम यह भी जानते हैं कि primes
सरणी की लंबाई के रूप में कैश किया गया है prime_count
! (यह इरादे और निश्चित आकार का संकेत है)।
हम यह भी जानते हैं कि अधिकांश इंजन शुरू में Arrays को कॉपी-ऑन-संशोधित (जब जरूरत होती है) के रूप में पास करते हैं जो उन्हें बहुत अधिक तेजी से नियंत्रित करता है (यदि आप उन्हें नहीं बदलते हैं)।
इसलिए यह मान लेना उचित है कि एरियर primes
आंतरिक रूप से पहले से ही एक अनुकूलित सरणी है जो निर्माण के बाद परिवर्तित नहीं होती है (रचनाकार के लिए जानना आसान है अगर सृजन के बाद कोई कोड नहीं है) और इसलिए पहले से ही (यदि लागू हो तो) इंजन) एक अनुकूलित तरीके से संग्रहीत, बहुत ज्यादा के रूप में अगर यह एक था Typed Array
।
जैसा कि मैंने अपने sum
फ़ंक्शन उदाहरण के साथ स्पष्ट करने की कोशिश की है , तर्क (ओं) जो पास हो जाते हैं, जो वास्तव में होने की जरूरत है और इस तरह से कि विशेष कोड मशीन-कोड के लिए कैसे संकलित किया जा रहा है। एक पासिंग String
के लिए sum
समारोह स्ट्रिंग लेकिन परिवर्तन कैसे समारोह JIT-संकलित किया गया है परिवर्तन नहीं होना चाहिए! sum
मशीन-कोड के संस्करण को पास करने के लिए एक एरे को पास करना एक अलग (शायद इस प्रकार के लिए अतिरिक्त, या 'आकार' के रूप में वे इसे कहते हैं, जो पास हो गया है)।
जैसा कि टाइप-ए- primes
एरे की तरह एअर- ऑन-फ्लाई को some_else में बदलना थोड़ा बोनकस लगता है, जबकि कंपाइलर जानता है कि यह फ़ंक्शन इसे संशोधित करने वाला भी नहीं है!
इन मान्यताओं के तहत जो 2 विकल्प छोड़ता है:
- नंबर-क्रंचर के रूप में संकलित करें कि कोई आउट-ऑफ-बाउंड्स नहीं है, अंत में आउट-ऑफ-बाउंड्स समस्या में चलाएं, recompile और redo work (जैसा कि ऊपर 1 संपादित में सैद्धांतिक उदाहरण में उल्लिखित है)
- कंपाइलर पहले से ही पता चला है (या संदिग्ध?) बाउंड एसेस ऊपर-सामने और फ़ंक्शन जेआईटी-संकलित था जैसे कि तर्क पास हुआ एक स्पार्स ऑब्जेक्ट था जिसके परिणामस्वरूप धीमी कार्यात्मक मशीन-कोड होता है (क्योंकि इसमें अधिक चेक / बातचीत / ज़बरदस्ती होगी। आदि।)। दूसरे शब्दों में: फ़ंक्शन को कुछ ऑप्टिमाइज़ेशन के लिए कभी भी योग्य नहीं किया गया था, इसे संकलित किया गया था जैसे कि यह एक 'विरल सरणी' (- जैसे) तर्क प्राप्त करता है।
मुझे अब वास्तव में आश्चर्य है कि इन 2 में से कौन सा है!
<=
और<
सिद्धांत में है और सभी आधुनिक प्रोसेसर (और दुभाषियों) में वास्तविक क्रियान्वयन में दोनों समान है।