जावास्क्रिप्ट में चर घोषणा के संदर्भ में कोड प्रदर्शन की निर्भरता


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

मेरे लेख के अंत में "परिणामी जावास्क्रिप्ट घोषणाओं और परिणामी जावास्क्रिप्ट बंद होने की विशेषताओं का उपयोग करना", मैंने लूप में लेट (लेक्सिकलडक्लेरेशन) और var (VarDeclaredNames) चर घोषणाओं के प्रदर्शन की तुलना पर संक्षेप में छुआ। तुलना के लिए, हमने एरे को छांटते हुए (बिना Array.prototype.sort () की मदद के ) मैनुअल रनटाइम का उपयोग किया, सबसे सरल तरीकों में से एक चयन द्वारा छंटनी है, क्योंकि 100,000 की औसत लंबाई के साथ हमें 5 बिलियन से थोड़ा अधिक मिला। दो चक्रों (बाह्य और नेस्टेड) ​​में पुनरावृत्तियों, और, इस राशि को अंत में एक पर्याप्त अनुमान देना चाहिए।

Var के लिए, यह दृश्य को सॉर्ट कर रहा था:

for (var i = 0, len = arr.length; i < len-1; i++) { var min, mini = i; for (var j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } //   Firefox: 9.082 . //   Chrome: 10.783 . 

और जाने के लिए :

 for (let i = 0, len = arr.length; i < len-1; i++) { let min, mini = i; for (let j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } //   Firefox: 5.261 . //   Chrome: 5.391 . 

इन नंबरों को देखकर, ऐसा लगता है, यह असमान रूप से तर्क दिया जा सकता है कि विज्ञापनों को पूरी तरह से गति में संस्करण को पार करने दें । लेकिन, इस निष्कर्ष के अलावा, यह सवाल हवा में बना रहा: अगर हम छोरों के लिए घोषणाएं करते हैं तो क्या होगा?

लेकिन, ऐसा करने से पहले, आपको ECMAScript 2019 (ECMA-262) के वर्तमान विनिर्देश द्वारा निर्देशित, लूप के काम में गहराई से उतरने की आवश्यकता है:

 13.7.4.7Runtime Semantics: LabelledEvaluation With parameter labelSet. IterationStatement':'for(Expression;Expression;Expression)Statement 1. If the first Expression is present, then a. Let exprRef be the result of evaluating the first Expression. b. Perform ? GetValue(exprRef). 2. Return ? ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet). IterationStatement':'for(varVariableDeclarationList;Expression;Expression)Statement 1. Let varDcl be the result of evaluating VariableDeclarationList. 2. ReturnIfAbrupt(varDcl). 3. Return ? ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet). IterationStatement':'for(LexicalDeclarationExpression;Expression)Statement 1. Let oldEnv be the running execution context's LexicalEnvironment. 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv). 3. Let loopEnvRec be loopEnv's EnvironmentRecord. 4. Let isConst be the result of performing IsConstantDeclaration of LexicalDeclaration. 5. Let boundNames be the BoundNames of LexicalDeclaration. 6. For each element dn of boundNames, do a. If isConst is true, then i. Perform ! loopEnvRec.CreateImmutableBinding(dn, true). b. Else, i. Perform ! loopEnvRec.CreateMutableBinding(dn, false). 7. Set the running execution context's LexicalEnvironment to loopEnv. 8. Let forDcl be the result of evaluating LexicalDeclaration. 9. If forDcl is an abrupt completion, then a. Set the running execution context's LexicalEnvironment to oldEnv. b. Return Completion(forDcl). 10. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ». 11. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet). 12. Set the running execution context's LexicalEnvironment to oldEnv. 13. Return Completion(bodyResult). 
ध्यान दें: IterationStatements के बाद कॉलोन, स्रोत में apostrophes द्वारा तैयार नहीं किए जाते हैं - यहां जोड़ दिए जाते हैं ताकि कोई स्वचालित स्वरूपण न हो जो पाठ की पठनीयता को बहुत खराब कर दे।

यहाँ, जैसा कि हम देखते हैं, लूप के लिए कॉलिंग और आगे के काम के लिए तीन विकल्प हैं:
  • के साथ (अभिव्यक्ति; अभिव्यक्ति; अभिव्यक्ति) वक्तव्य
    ForBodyEvaluation (दूसरा एक्सप्रेशन, तीसरा एक्सप्रेशन, स्टेटमेंट, "", labelSet)
  • साथ (varVariableDeclarationList; अभिव्यक्ति; अभिव्यक्ति) कथन
    ForBodyEvaluation (पहला एक्सप्रेशन, दूसरा एक्सप्रेशन, स्टेटमेंट, "", labelSet)।
  • पर (LexicalDeclarationExpression; अभिव्यक्ति) कथन
    ForBodyEvaluation (पहला एक्सप्रेशन, दूसरा एक्सप्रेशन, स्टेटमेंट, पेरिलेशन लेट्स, लेबलसेट)

आखिरी, तीसरे संस्करण में, पहले दो के विपरीत, चौथा पैरामीटर खाली नहीं है - पेरिटेरिएशनलेट्स - ये वास्तव में लूप के लिए पारित पहले पैरामीटर में एक ही लेट -घोषणाएं हैं। वे अनुच्छेद 10 में निर्दिष्ट हैं:
- यदि गलत है , तो गलत हो जाने दो। अन्यथा perIterationLets "" होने दें।
यदि एक स्थिरांक के लिए पारित किया गया था, लेकिन एक चर नहीं है, तो प्रतिगामीकरण पैरामीटर रिक्त हो जाता है।

इसके अलावा, तीसरे विकल्प में, पैरा 2 पर ध्यान देना आवश्यक है:
- आज्ञा दें

 8.1.2.2NewDeclarativeEnvironment ( E ) When the abstract operation NewDeclarativeEnvironment is called with a Lexical Environment as argument E the following steps are performed: 1. Let env be a new Lexical Environment. 2. Let envRec be a new declarative Environment Record containing no bindings. 3. Set env's EnvironmentRecord to envRec. 4. Set the outer lexical environment reference of env to E. 5. Return env. 

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

और हम याद करते हैं कि चलो और कॉन्स्टेबल वेरिएबल डिक्लेरेशन प्रासंगिक रूप से उस ब्लॉक से बँधे हुए हैं जिसमें वे घोषित किए गए हैं।

 13.2.14Runtime Semantics: BlockDeclarationInstantiation ( code, env ) Note When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record. BlockDeclarationInstantiation is performed as follows using arguments code and env. code is the Parse Node corresponding to the body of the block. env is the Lexical Environment in which bindings are to be created. 1. Let envRec be env's EnvironmentRecord. 2. Assert: envRec is a declarative Environment Record. 3. Let declarations be the LexicallyScopedDeclarations of code. 4. For each element d in declarations, do a. For each element dn of the BoundNames of d, do i. If IsConstantDeclaration of d is true, then 1. Perform ! envRec.CreateImmutableBinding(dn, true). ii. Else, 1. Perform ! envRec.CreateMutableBinding(dn, false). b. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then i. Let fn be the sole element of the BoundNames of d. ii. Let fo be the result of performing InstantiateFunctionObject for d with argument env. iii. Perform envRec.InitializeBinding(fn, fo). 

नोट: चूंकि लूप के लिए कॉलिंग के पहले दो वेरिएंट में ऐसी कोई घोषणा नहीं थी, इसलिए उनके लिए एक नया वातावरण बनाने की कोई आवश्यकता नहीं थी।

हम आगे बढ़ते हैं और विचार करते हैं कि ForBodyEvaluation क्या है :

 13.7.4.8Runtime Semantics: ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet ) The abstract operation ForBodyEvaluation with arguments test, increment, stmt, perIterationBindings, and labelSet is performed as follows: 1. Let V be undefined. 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). 3. Repeat, a. If test is not [empty], then i. Let testRef be the result of evaluating test. ii. Let testValue be ? GetValue(testRef). iii. If ToBoolean(testValue) is false, return NormalCompletion(V). b. Let result be the result of evaluating stmt. c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)). d. If result.[[Value]] is not empty, set V to result.[[Value]]. e. Perform ? CreatePerIterationEnvironment(perIterationBindings). f. If increment is not [empty], then i. Let incRef be the result of evaluating increment. ii. Perform ? GetValue(incRef). 

आपको पहले किस पर ध्यान देना चाहिए:
  • आने वाले मापदंडों का वर्णन:
    • परीक्षण : लूप बॉडी के अगले पुनरावृत्ति से पहले सत्य के लिए जाँच की गई अभिव्यक्ति (उदाहरण के लिए: i <len );
    • वेतन वृद्धि : प्रत्येक नए पुनरावृत्ति की शुरुआत में अभिव्यक्ति का मूल्यांकन (पहले को छोड़कर) (उदाहरण के लिए: i ++ );
    • stmt : लूप बॉडी
    • perIterationBindings : वेरिएबल्स को पहले पैरामीटर के लिए पहले से घोषित किया जाता है (उदाहरण के लिए: चलो i = 0। मुझे जाने दो || मुझे i, j )?
    • लेबलसेट : लूप का लेबल;
  • बिंदु 2: यहां, यदि गैर-रिक्त पैरामीटर perIterationBindings पास है, तो लूप के प्रारंभिक पास को निष्पादित करने के लिए एक दूसरा वातावरण बनाया जाता है;
  • पैराग्राफ 3. ए: चक्र के निष्पादन को जारी रखने के लिए दी गई स्थिति की जांच करना;
  • खंड 3. बी: चक्र शरीर का निष्पादन;
  • बिंदु 3. ई: एक नया वातावरण बनाना।

ठीक है, और, सीधे, लूप के लिए आंतरिक वातावरण बनाने के लिए एल्गोरिथ्म:

 13.7.4.9Runtime Semantics: CreatePerIterationEnvironment ( perIterationBindings ) 1. The abstract operation CreatePerIterationEnvironment with argument perIterationBindings is performed as follows: 1. If perIterationBindings has any elements, then a. Let lastIterationEnv be the running execution context's LexicalEnvironment. b. Let lastIterationEnvRec be lastIterationEnv's EnvironmentRecord. c. Let outer be lastIterationEnv's outer environment reference. d. Assert: outer is not null. e. Let thisIterationEnv be NewDeclarativeEnvironment(outer). f. Let thisIterationEnvRec be thisIterationEnv's EnvironmentRecord. g. For each element bn of perIterationBindings, do i. Perform ! thisIterationEnvRec.CreateMutableBinding(bn, false). ii. Let lastValue be ? lastIterationEnvRec.GetBindingValue(bn, true). iii. Perform thisIterationEnvRec.InitializeBinding(bn, lastValue). h. Set the running execution context's LexicalEnvironment to thisIterationEnv. 2. Return undefined. 

जैसा कि हम देख सकते हैं, पारित पैरामीटर में किसी भी तत्व की उपस्थिति के लिए पहला पैराग्राफ चेक करता है, और पैरा 1 केवल तभी किया जाता है यदि कोई घोषणाएं हैं। सभी नए वातावरण एक ही बाहरी संदर्भ के संदर्भ में बनाए जाते हैं और लेट वेरिएबल्स के नए बाइंडिंग के रूप में पिछले चलना (पिछले काम के माहौल) से नवीनतम मान लेते हैं।

एक उदाहरण के रूप में, एक समान अभिव्यक्ति पर विचार करें:

 let arr = []; for (let i = 0; i < 3; i++) { arr.push(i); } console.log(arr); // Array(3) [ 0, 1, 2 ] 

और यहां बताया गया है कि किस तरह से इसका इस्तेमाल बिना किसी परम्परागत परंपरा के किया जा सकता है:

 let arr = []; //    { let i = 0; //     for } //   ,   { let i = 0; //    i    if (i < 3) arr.push(i); } //    { let i = 0; //    i    i++; if (i < 3) arr.push(i); } //    { let i = 1; //    i    i++; if (i < 3) arr.push(i); } //    { let i = 2; //    i    i++; if (i < 3) arr.push(i); } console.log(arr); // Array(3) [ 0, 1, 2 ] 

वास्तव में, हम इस निष्कर्ष पर आते हैं कि प्रत्येक संदर्भ के लिए, और यहां हम उनमें से पांच हैं, हम नए चर को पहले पैरामीटर के रूप में घोषित करने के लिए नए बाइंडिंग बनाते हैं (महत्वपूर्ण: यह लूप के शरीर में सीधे घोषणाओं को लागू करने के लिए लागू नहीं होता है)।

यहां बताया गया है कि, उदाहरण के लिए, जब अतिरिक्त बाइंडिंग नहीं होती है तो यह लूप var का उपयोग करते समय दिखेगा:

 let arr2 = []; var i = 0; if (i < 3) arr.push(i); i++; if (i < 3) arr.push(i); i++; if (i < 3) arr.push(i); i++; if (i < 3) arr.push(i); console.log(arr); // Array(3) [ 0, 1, 2 ] 

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

आइए ऐसा करने की कोशिश करते हैं, उदाहरण के रूप में 100,000 तत्वों की एक ही छँटाई का उपयोग करते हुए, और सुंदरता के लिए, हम इससे पहले सभी अन्य चर की परिभाषा भी बनाते हैं:

 let i, j, min, mini, len = arr.length; for (i = 0; i < len-1; i++) { mini = i; for (j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } //   Firefox: 34.246 . //   Chrome: 10.803 . 

अप्रत्याशित परिणाम ... जो अपेक्षित था, उसके ठीक उलट। इस परीक्षण में फ़ायरफ़ॉक्स की ख़ासियत हड़ताली है।

लगभग। यह काम नहीं किया, आइए और जम्मू चर की घोषणा को वापस इसी चक्र के मापदंडों पर लौटाएं:

 let min, mini, len = arr.length; for (let i = 0; i < len-1; i++) { mini = i; for (let j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } //   Firefox: 6.575 . //   Chrome: 6.749 . 

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

यही है, यह पता चला है कि लूप के पैरामीटर या शरीर में घोषित चर के साथ काम करना, इसके बाहर की तुलना में बहुत तेज होता है।

लेकिन, हमें लूप के लिए विनिर्देशन में कोई भी "टर्बो" निर्देश देखने के लिए प्रतीत नहीं हुआ जो हमें इस तरह के विचार के लिए ले जा सके। इसलिए, यह विशेष रूप से लूप के काम की बारीकियों के बारे में नहीं है, लेकिन कुछ और ... उदाहरण के लिए, लेट डिक्लेरेशन की विशेषताएं: मुख्य विशेषता क्या है जो var से ले जाने में अंतर करती है? ब्लॉक निष्पादन संदर्भ! और हमारे पिछले दो उदाहरणों में, हमने ब्लॉक के बाहर विज्ञापनों का उपयोग किया। लेकिन, क्या होगा अगर इन घोषणाओं को वापस करने के बजाय , हम सिर्फ उनके लिए एक अलग ब्लॉक का चयन करें?

 { let i, j, min, mini, len = arr.length; for (i = 0; i < len-1; i++) { mini = i; for (j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } } //   Firefox: 5.262 . //   Chrome: 5.405 . 

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

और यहां दूसरे को याद करने के लिए अच्छा होगा, थोड़ा-सा शापित तरीका है - चर घोषित करने का तरीका।

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

 function test() { var i, j, min, mini, len = arr.length; for (i = 0; i < len-1; i++) { mini = i; for (j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } } test(); //   Firefox: 5.255 . //   Chrome: 5.411 . 

और, हमें लगभग समान परिणाम मिला जो लेट का उपयोग करते समय था।

अंत में, आइए जांच करें कि मंदी वैश्विक मान को उसके मूल्य को बदले बिना पढ़ती है या नहीं।

चलो

 let len = arr.length; for (let i = 0; i < len-1; i++) { let min, mini = i; for (let j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } //   Firefox: 5.262 . //   Chrome: 5.391 . 

वर

 var len = arr.length; function test() { var i, j, min, mini; for (i = 0; i < len-1; i++) { mini = i; for (j = i+1; j < len; j++) { if (arr[mini] > arr[j]) mini = j; } min = arr[mini]; arr[mini] = arr[i]; arr[i] = min; } } test(); //   Firefox: 5.258 . //   Chrome: 5.439 . 

परिणाम बताते हैं कि वैश्विक चर को पढ़ने से निष्पादन समय प्रभावित नहीं हुआ।

संक्षेप में देना


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

संदर्भ


ECMAScript 2019 (ECMA-262)
जावास्क्रिप्ट में परिणामी क्लोजर के चर और सुविधाओं की घोषणाओं का उपयोग करना

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


All Articles