प्रस्तावना
डैनियल क्लिफोर्ड ने Google I / O पर V8 इंजन के लिए जावास्क्रिप्ट कोड को अनुकूलित करने की सुविधाओं के बारे में एक उत्कृष्ट बात की। डैनियल ने हमें और अधिक गति के लिए प्रयास करने के लिए प्रोत्साहित किया, ध्यान से सी ++ और जावास्क्रिप्ट के बीच के अंतरों का विश्लेषण किया और कोड लिखकर याद किया कि दुभाषिया कैसे काम करता है। मैंने इस लेख में डैनियल के प्रदर्शन के सबसे महत्वपूर्ण बिंदुओं का सारांश एकत्र किया है, और मैं इसे इंजन में बदलाव के रूप में अपडेट करूंगा।
सबसे महत्वपूर्ण सलाह
किसी भी प्रदर्शन सुझाव को संदर्भ में देना बहुत महत्वपूर्ण है। अनुकूलन अक्सर एक जुनून बन जाता है, और जंगल में गहरा विसर्जन वास्तव में अधिक महत्वपूर्ण चीजों से ध्यान भंग कर सकता है। आपको वेब एप्लिकेशन प्रदर्शन के समग्र दृष्टिकोण की आवश्यकता है - इन अनुकूलन युक्तियों पर ध्यान देने से पहले, आपको अपने कोड का विश्लेषण करना चाहिए जैसे
पेजस्पीड और पहले समग्र परिणाम प्राप्त करना। यह समय से पहले अनुकूलन से बचने में मदद करेगा।
तेज़ वेब एप्लिकेशन बनाने के लिए सबसे अच्छी रणनीति है:
- समस्याओं में चलाने से पहले इसे सोचें।
- समझना और समस्या के मूल में आना।
- केवल वही मायने रखता है जो मायने रखता है।
इस रणनीति का पालन करने के लिए, यह समझना महत्वपूर्ण है कि वी 8 जेएस का अनुकूलन कैसे करता है, यह कल्पना करने के लिए कि सब कुछ रनटाइम पर कैसे होता है। सही उपकरण होना भी जरूरी है। अपने भाषण में, डैनियल ने डेवलपर टूल के लिए अधिक समय समर्पित किया; इस लेख में, मैं मुख्य रूप से V8 वास्तुकला की विशेषताओं को देखता हूं।
तो चलिए शुरू करते हैं।
छिपी हुई कक्षाएं
संकलन के चरण में, जावास्क्रिप्ट में प्रकारों के बारे में जानकारी बहुत सीमित है: प्रकार रनटाइम में बदल सकते हैं, इसलिए यह उम्मीद करना स्वाभाविक है कि संकलन के दौरान उनके बारे में धारणा बनाना मुश्किल है। सवाल उठता है - कैसे, ऐसी स्थितियों में, क्या कोई भी C ++ की गति के करीब पहुंच सकता है? हालाँकि, V8 रनटाइम पर ऑब्जेक्ट्स के लिए हिडन क्लासेस बनाने का प्रबंधन करता है। समान वर्ग वाली वस्तुएँ समान अनुकूलित कोड साझा करती हैं।
उदाहरण के लिए:
function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(11, 22); var p2 = new Point(33, 44);
जब तक संकलक के अंदर "
.z
" संपत्ति को
p2
,
p1
और
p2
में जोड़ा गया, तब तक एक ही छिपा हुआ वर्ग था, और V8 दोनों वस्तुओं के लिए एक ही अनुकूलित मशीन कोड का उपयोग कर सकता था। जितनी बार आप छिपे हुए वर्ग को बदलते हैं, उतना ही बेहतर प्रदर्शन होता है।
निष्कर्ष:
- कंस्ट्रक्टर्स में सभी ऑब्जेक्ट्स को इनिशियलाइज़ करें ताकि वे भविष्य में कम से कम बदल सकें।
- किसी वस्तु के गुणों को हमेशा एक ही क्रम में शुरू करें।
संख्या
V8 इस बात पर नज़र रखता है कि आप चर का उपयोग कैसे करते हैं, और प्रत्येक प्रकार के लिए सबसे कुशल प्रतिनिधित्व का उपयोग करता है। प्रकार बदलना काफी महंगा हो सकता है, इसलिए फ्लोटिंग पॉइंट नंबरों और पूर्णांकों को न मिलाने का प्रयास करें। सामान्य तौर पर, पूर्णांक का उपयोग करना बेहतर होता है।
उदाहरण के लिए:
var i = 42;
निष्कर्ष:
- जहाँ भी संभव हो 31-बिट हस्ताक्षरित पूर्णांक का उपयोग करने का प्रयास करें।
सरणियों
V8 सरणियों के आंतरिक प्रतिनिधित्व के दो प्रकारों का उपयोग करता है:
- कॉम्पैक्ट अनुक्रमिक कुंजी सेट के लिए वास्तविक सरणियाँ।
- अन्य मामलों में हैश टेबल।
निष्कर्ष:
- सरणियों को एक श्रेणी से दूसरी श्रेणी में जाने के लिए मजबूर न करें।
- 0 पर शुरू होने वाले निरंतर अनुक्रमांक का उपयोग करें (बिल्कुल सी की तरह)।
- बड़े सरणियों (64K से अधिक तत्वों से युक्त) को पूर्वनिर्मित न करें - यह कुछ भी नहीं करेगा।
- एरे से तत्वों को न निकालें (विशेषकर संख्यात्मक)।
- असंवैधानिक या हटाए गए आइटम का उपयोग न करें। एक उदाहरण:
a = new Array(); for (var b = 0; b < 10; b++) { a[0] |= b;
दोहरे परिशुद्धता के साथ संख्याओं की संख्या सबसे अधिक तेज़ी से काम करती है - उनमें मानों को अनपैक किया जाता है और प्राथमिक प्रकारों के रूप में संग्रहीत किया जाता है, न कि वस्तुओं के रूप में। सरणियों के विचारहीन उपयोग से अक्सर अनपैकिंग हो सकती है:
var a = new Array(); a[0] = 77;
यह इस तरह बहुत तेज होगा:
var a = [77, 88, 0.5, true];
पहले उदाहरण में, अलग-अलग असाइनमेंट क्रमिक रूप से होते हैं, और जिस समय a[2]
का मूल्य मिलता है, कंपाइलर डबल सटीकता के साथ अनपैक किए गए नंबरों की एक सरणी में परिवर्तित हो जाता है, और जब a[3]
एक गैर-संख्यात्मक तत्व के साथ आरंभीकृत होता है, तो उलटा परिवर्तन होता है। दूसरे उदाहरण में, कंपाइलर तुरंत वांछित सरणी प्रकार का चयन करेगा।
इस तरह से:
- एक सरणी शाब्दिक का उपयोग करके छोटे निश्चित सरणियों को सबसे पहले आरंभिक किया जाता है।
- उपयोग करने से पहले छोटे ऐरे (<64K) भरें।
- सांख्यिक सरणियों में गैर-संख्यात्मक मानों को संग्रहीत न करें।
- जब शाब्दिक के माध्यम से आरंभ न करें तो रूपांतरणों से बचने का प्रयास करें।
जावास्क्रिप्ट संकलन
यद्यपि जावास्क्रिप्ट एक गतिशील भाषा है, और मूल रूप से व्याख्या की गई थी, सभी आधुनिक इंजन वास्तव में संकलक हैं। V8 में दो कंपाइलर एक साथ काम करते हैं:
- आधार संकलक जो संपूर्ण स्क्रिप्ट के लिए कोड उत्पन्न करता है।
- एक अनुकूलन कंपाइलर जो सबसे तेज़ साइटों के लिए बहुत तेज़ कोड उत्पन्न करता है। इस संकलन में अधिक समय लगता है।
आधार संकलक
V8 में, वह सबसे पहले सभी कोड को प्रोसेस करना शुरू करता है और इसे जल्दी से जल्दी चलाता है। इसके द्वारा उत्पन्न कोड लगभग अनुकूलित नहीं है - बुनियादी संकलक प्रकारों के बारे में लगभग कोई धारणा नहीं बनाता है। निष्पादन के दौरान, संकलक इनलाइन कैश का उपयोग करता है जिसमें कोड के प्रकार-निर्भर अनुभाग संग्रहीत होते हैं। जब इस कोड को पुनः आरंभ किया जाता है, तो संकलक कैश से तैयार कोड के उपयुक्त संस्करण को चुनने से पहले इसमें उपयोग किए गए प्रकारों की जांच करता है। इसलिए, ऑपरेटर जो विभिन्न प्रकारों के साथ काम कर सकते हैं, निष्पादित करने के लिए धीमी हैं।
निष्कर्ष:
- बहुरूपियों के लिए मोनोमोर्फिक ऑपरेटरों को प्राथमिकता दें।
एक ऑपरेटर मोनोमोर्फिक है यदि छिपे हुए प्रकार के ऑपरेंड हमेशा समान होते हैं, और पॉलीमोर्फिक अगर यह बदल सकता है। उदाहरण के लिए,
add()
लिए दूसरा कॉल
add()
कोड बहुरूपी बनाता है:
function add(x, y) { return x + y; } add(1, 2);
संकलक का अनुकूलन
बुनियादी संकलक के काम के साथ समानांतर में, अनुकूलन करने वाला कंपाइलर कोड के "गर्म" खंडों को फिर से जोड़ देता है जो अक्सर निष्पादित होते हैं। यह इनलाइन कैश में संचित जानकारी का उपयोग करता है।
अनुकूलन करने वाला कंपाइलर कॉल स्थानों में कार्यों को एम्बेड करने की कोशिश करता है, जो निष्पादन को गति देता है (लेकिन मेमोरी की खपत को बढ़ाता है) और अतिरिक्त अनुकूलन के लिए अनुमति देता है। मोनोमोर्फिक फ़ंक्शन और कंस्ट्रक्टर आसानी से पूरी तरह से बनाए जा सकते हैं, और यह एक और कारण है कि आपको उनका उपयोग करने का प्रयास करना चाहिए।
आप देख सकते हैं कि d8 इंजन के स्टैंडअलोन संस्करण का उपयोग करके आपके कोड में वास्तव में क्या अनुकूलित है:
d8 --trace-opt primes.js
(अनुकूलित कार्यों के नाम stdout
में प्रदर्शित किए जाएंगे)सभी सुविधाओं को अनुकूलित नहीं किया जा सकता है। विशेष रूप से, अनुकूलन करने वाला कंपाइलर किसी भी फ़ंक्शंस को रोक देता है जिसमें
try/catch
ब्लॉक होते हैं।
निष्कर्ष:
यदि आपको एक
try/catch
का उपयोग करने की आवश्यकता है, तो प्रदर्शन-महत्वपूर्ण कोड बाहर रखें। एक उदाहरण:
function perf_sensitive() {
शायद भविष्य में स्थिति बदल जाएगी, और हम अनुकूलन कंपाइलर द्वारा
try/catch
ब्लॉक संकलित
try/catch
सक्षम होंगे। आप यह देख सकते हैं कि d8 शुरू करते समय
--trace-bailout
को निर्दिष्ट करके किन कार्यों को अनदेखा किया गया है:
d8 --trace-bailout primes.js
deoptimization
ऑप्टिमाइज़िंग कंपाइलर द्वारा उत्पन्न कोड हमेशा तेज नहीं होता है। इस मामले में, मूल, गैर-अनुकूलित संस्करण का उपयोग किया जाता है। असफल रूप से अनुकूलित कोड को फेंक दिया जाता है, और बेस कंपाइलर द्वारा बनाए गए कोड में उचित स्थान से निष्पादन जारी रहता है। शायद यह कोड जल्द ही फिर से अनुकूलित हो जाएगा, अगर परिस्थितियाँ अनुमति देती हैं। विशेष रूप से, पहले से ही अनुकूलित कोड के अंदर छिपे हुए वर्गों को बदलने से विकृति हो जाती है।
निष्कर्ष:
- अनुकूलित कार्यों में छिपी हुई कक्षाओं को बदलने से बचें।
आप वास्तव में देख सकते हैं कि कौन से फ़ंक्शंस को d8
--trace-deopt
the
--trace-deopt
साथ चलाकर समाप्त किया जा रहा
--trace-deopt
:
d8 --trace-deopt primes.js
अन्य V8 उपकरण
उपरोक्त कार्यों को स्टार्टअप पर Google Chrome में स्थानांतरित किया जा सकता है:
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt --trace-bailout
डी 8 में एक प्रोफाइलर भी है:
d8 primes.js --prof
डी 8 नमूना प्रोफाइलर हर मिलीसेकंड को स्नैपशॉट लेता है और
v8.log
को लिखता है।
सारांश
यह समझना महत्वपूर्ण है कि अच्छी तरह से अनुकूलित कोड लिखने के लिए V8 इंजन कैसे काम करता है। और लेख की शुरुआत में वर्णित सामान्य सिद्धांतों के बारे में मत भूलना:
- समस्याओं में चलाने से पहले इसे सोचें।
- समझना और समस्या के मूल में आना।
- केवल वही मायने रखता है जो मायने रखता है।
इसका मतलब यह है कि आपको यह सुनिश्चित करना होगा कि पेजस्पीड जैसे टूल का उपयोग करके यह जावास्क्रिप्ट में है। अड़चन की तलाश करने से पहले डोम कॉल से छुटकारा पाने के लायक हो सकता है। मुझे उम्मीद है कि डैनियल की प्रस्तुति (और यह लेख) आपको V8 के संचालन को बेहतर ढंग से समझने में मदद करेगी, लेकिन यह मत भूलो कि एक विशिष्ट इंजन में समायोजित करने के बजाय प्रोग्राम एल्गोरिथ्म को अनुकूलित करने के लिए यह अक्सर अधिक उपयोगी होता है।
संदर्भ: