V8 में इस कोड स्निपेट का उपयोग करके <= धीमा क्यों है?


166

मैं स्लाइड्स पढ़ रहा हूँ ब्रेकिंग जावास्क्रिप्ट स्पीड लिमिट वी 8 के साथ , और नीचे दिए गए कोड की तरह एक उदाहरण है। मैं यह पता नहीं लगा सकता कि इस मामले में क्यों <=धीमी है <, क्या कोई यह समझा सकता है? किसी भी टिप्पणी की सराहना की है।

धीरे:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

(संकेत: प्राइम लंबाई की एक सरणी है प्राइम_काउंट)

और तेज:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i < this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

[अधिक जानकारी] मेरे स्थानीय पर्यावरण परीक्षण में गति में सुधार महत्वपूर्ण है, परिणाम इस प्रकार हैं:

V8 version 7.3.0 (candidate) 

धीरे:

 time d8 prime.js
 287107
 12.71 user 
 0.05 system 
 0:12.84 elapsed 

और तेज:

time d8 prime.js
287107
1.82 user 
0.01 system 
0:01.84 elapsed

10
की कम्प्युटेशनल जटिलता @DacreDenny <=और <सिद्धांत में है और सभी आधुनिक प्रोसेसर (और दुभाषियों) में वास्तविक क्रियान्वयन में दोनों समान है।
टाइपिया

1
मैंने दस्तावेज़ पढ़ा है, एक mainकोड है जो उस फ़ंक्शन को लूप में कॉल करता है जो 25000कई बार चलता है, इसलिए आप उस परिवर्तन को करने के लिए बहुत कम पुनरावृत्तियों कर रहे हैं। इसके अलावा, यदि किसी ऐरे में 5 की कमी है, तो एरर्स इंडेक्सिंग शुरू करने के बाद से array[5]उसकी undefinedवैल्यू देते हुए उसकी सीमा से बाहर जाने की कोशिश करेगा 0
Shidersz

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

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

3
यह एक ऐसे संस्करण की तुलना करने के लिए उपयोगी हो सकता है जो उपयोग करता है <=लेकिन अन्यथा <संस्करण के लिए पहचान के रूप में कार्य करता है i <= this.prime_count - 1। यह "अतिरिक्त पुनरावृत्ति" समस्या और "सरणी के अंत में एक अतीत" समस्या को हल करता है।
TheHansinator

जवाबों:


132

मैं Google पर V8 पर काम करता हूं, और मौजूदा उत्तरों और टिप्पणियों के शीर्ष पर कुछ अतिरिक्त जानकारी प्रदान करना चाहता था।

संदर्भ के लिए, यहाँ स्लाइड से पूर्ण कोड उदाहरण दिया गया है :

var iterations = 25000;

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(iterations);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }
  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  var p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < iterations) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

सबसे पहले और सबसे महत्वपूर्ण, प्रदर्शन अंतर का सीधे <और <=ऑपरेटरों से कोई लेना-देना नहीं है । तो कृपया <=अपने कोड से बचने के लिए हुप्स के माध्यम से कूद न करें क्योंकि आप स्टैक ओवरफ्लो पर पढ़ते हैं कि यह धीमा है --- यह नहीं है!


दूसरा, लोगों ने बताया कि सरणी "छिद्रपूर्ण" है। यह ओपी के पोस्ट में कोड स्निपेट से स्पष्ट नहीं था, लेकिन जब आप कोड को देखते हैं तो यह स्पष्ट होता है this.primes:

this.primes = new Array(iterations);

यह V8 में एक HOLEYतत्व प्रकार के साथ एक सरणी में परिणाम देता है , भले ही सरणी पूरी तरह से भरा / पैक / सन्निहित हो। सामान्य तौर पर, छिद्रित सरणियों पर संचालन पैक किए गए सरणियों पर संचालन की तुलना में धीमा होता है, लेकिन इस मामले में अंतर नगण्य है: यह 1 अतिरिक्त एसएमआई ( छोटे पूर्णांक ) चेक (छेदों के खिलाफ गार्ड) की मात्रा में होता है जब भी हम this.primes[i]लूप के भीतर हिट करते हैं isPrimeDivisible। कोई बड़ी बात नहीं!

TL; DR सरणी HOLEYयहाँ समस्या नहीं है।


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

आउट-ऑफ-बाउंड्स इस पंक्ति में this.primes[i]होने के परिणामस्वरूप पढ़ते हैं undefined:

if ((candidate % this.primes[i]) == 0) return true;

और यह हमें वास्तविक मुद्दे पर लाता है : %ऑपरेटर अब गैर-पूर्णांक ऑपरेंड के साथ उपयोग किया जा रहा है!

  • integer % someOtherIntegerबहुत कुशलता से गणना की जा सकती है; जावास्क्रिप्ट इंजन इस मामले के लिए अत्यधिक अनुकूलित मशीन कोड का उत्पादन कर सकते हैं।

  • integer % undefinedदूसरी ओर राशियों को कम कुशल तरीके Float64Modसे, क्योंकि undefinedएक दोहरे के रूप में दर्शाया गया है।

कोड स्निपेट को वास्तव <=में <इस लाइन में बदलकर सुधारा जा सकता है :

for (var i = 1; i <= this.prime_count; ++i) {

... इसलिए नहीं कि <=किसी तरह से एक बेहतर ऑपरेटर है <, लेकिन सिर्फ इसलिए कि इस विशेष मामले में पढ़े जाने वाले आउट-ऑफ-बाउंड्स से बचा जाता है।


1
टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
शमूएल एलवाई

1
100% पूर्ण होने के लिए, इस .primes [i] के लिए कुंजी लोड IC अप्रत्याशित रूप से V8 में मेगामोर्फिक जाता है। यह एक बग की तरह लगता है: bugs.chromium.org/p/v8/issues/detail?id=8561
Mathias Bynens

226

अन्य उत्तरों और टिप्पणियों में उल्लेख किया गया है कि दो छोरों के बीच का अंतर यह है कि पहला एक दूसरे की तुलना में एक अधिक पुनरावृत्ति निष्पादित करता है। यह सच है, लेकिन एक सरणी में जो 25,000 तत्वों तक बढ़ती है, कम या ज्यादा एक पुनरावृत्ति केवल एक मामूली अंतर बना देगी। एक बॉलपार्क के अनुमान के अनुसार, अगर हम औसत लंबाई मान लेते हैं कि यह 12,500 है, तो हम जो अंतर की उम्मीद कर सकते हैं वह लगभग 1 / 12,500, या केवल 0.008% होना चाहिए।

यहां प्रदर्शन का अंतर उस एक अतिरिक्त पुनरावृत्ति द्वारा समझाया जाएगा, और प्रस्तुति के अंत में समस्या को समझाया गया है।

this.primes एक सन्निहित सरणी है (प्रत्येक तत्व एक मान रखता है) और तत्व सभी संख्याएँ हैं।

जावास्क्रिप्ट इंजन ऐसे सरणी को वास्तविक संख्याओं का एक सरल सरणी होने के लिए अनुकूलित कर सकता है, वस्तुओं की एक सरणी के बजाय जो संख्याओं को समाहित करता है, लेकिन इसमें अन्य मान या कोई मान नहीं हो सकता है। पहला प्रारूप एक्सेस करने के लिए बहुत तेज़ है: इसमें कम कोड लगता है, और ऐरे बहुत छोटा होता है इसलिए यह कैश में बेहतर होगा। लेकिन कुछ शर्तें हैं जो इस अनुकूलित प्रारूप का उपयोग करने से रोक सकती हैं।

एक शर्त यह होगी कि कुछ एरे तत्व गायब हैं। उदाहरण के लिए:

let array = [];
a[0] = 10;
a[2] = 20;

अब इसका क्या मूल्य है a[1]? इसका कोई मूल्य नहीं है । (यह कहने के लिए भी सही नहीं है कि इसका मूल्य है undefined- एक सरणी तत्व जिसमें undefinedमूल्य एक सरणी तत्व से अलग है जो पूरी तरह से गायब है।)

केवल संख्याओं के साथ इसका प्रतिनिधित्व करने का कोई तरीका नहीं है, इसलिए जावास्क्रिप्ट इंजन को कम अनुकूलित प्रारूप का उपयोग करने के लिए मजबूर किया जाता है। यदि a[1]अन्य दो तत्वों की तरह एक संख्यात्मक मान निहित है, तो सरणी को संभवतः केवल संख्याओं की एक सरणी में अनुकूलित किया जा सकता है।

यदि आप प्रस्तुति में चर्चा की गई सरणी के सीमा के बाहर किसी तत्व तक पहुंचने का प्रयास करते हैं, तो व्यूह-निर्मित प्रारूप में एक सरणी के लिए मजबूर होने का दूसरा कारण हो सकता है।

<=सरणी के अंत में एक तत्व को पढ़ने के प्रयासों के साथ पहला लूप । एल्गोरिथ्म अभी भी सही ढंग से काम करता है, क्योंकि अंतिम अतिरिक्त पुनरावृत्ति में:

  • this.primes[i]undefinedक्योंकि iसरणी अंत अतीत है के लिए मूल्यांकन करता है ।
  • candidate % undefined(किसी भी मूल्य के लिए candidate) का मूल्यांकन करता है NaN
  • NaN == 0का मूल्यांकन करता है false
  • इसलिए, return trueनिष्पादित नहीं किया गया है।

तो यह ऐसा है जैसे अतिरिक्त पुनरावृत्ति कभी नहीं हुई - इसका बाकी तर्क पर कोई प्रभाव नहीं है। कोड अतिरिक्त पुनरावृत्ति के बिना ही परिणाम उत्पन्न करता है।

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

दूसरा लूप <केवल उन तत्वों को पढ़ता है जो सरणी के भीतर मौजूद हैं, इसलिए यह एक अनुकूलित सरणी और कोड की अनुमति देता है।

समस्या 90-91 के पन्नों में वर्णित है , उससे पहले और बाद के पृष्ठों में संबंधित चर्चा के साथ।

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

यदि V8 लेखक ने जो कहा वह अभी भी सत्य है, तो सरणी के अंत को पढ़ने से इसे अनुकूलित होने से रोका जा सकेगा और इसे धीमे स्वरूप में वापस आना होगा।

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


1
मुझे पूरा यकीन है कि सरणी अभी भी सन्निहित है - मेमोरी लेआउट को बदलने का कोई कारण नहीं है। हालांकि यह मायने रखता है कि संपत्ति की पहुंच में सूचकांक-बाहर की जाँच को अनुकूलित नहीं किया जा सकता है, और कभी-कभी कोड undefinedको एक अलग गणना के लिए अग्रणी संख्या के बजाय खिलाया जाता है।
बरगी

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

1
@Bergi गैर-अनुकूलित मामले में सरणी अभी भी सन्निहित हो सकती है, लेकिन सरणी तत्व अनुकूलित मामले में उसी प्रकार के नहीं हैं। अनुकूलित संस्करण संख्याओं का एक सरल सरणी है जिसमें कोई अतिरिक्त फ़ुल नहीं है। गैर-अनुकूलित संस्करण वस्तुओं का एक सरणी है (एक आंतरिक ऑब्जेक्ट प्रारूप, जावास्क्रिप्ट नहीं Object), क्योंकि इसे सरणी में डेटा प्रकारों के किसी भी मिश्रण का समर्थन करना है। जैसा कि मैंने ऊपर उल्लेख किया है, लूप में दिए गए कोड undefinedको एल्गोरिथ्म की शुद्धता को प्रभावित नहीं करता है - यह गणना को बिल्कुल भी नहीं बदलता है (ऐसा लगता है जैसे अतिरिक्त पुनरावृत्ति कभी नहीं हुई है)।
माइकल गिरी

3
@Bergi इस बात को बताने वाले V8 लेखक ने कहा कि सरणी सीमा के बाहर पढ़ा गया प्रयास सरणी का इलाज करने का कारण बनता है जैसे कि इसमें प्रकारों का मिश्रण था: अनुकूलित संख्या-केवल प्रारूप के बजाय, यह सरणी को वापस अनुकूलित करता है सामान्य प्रारूप। अनुकूलित मामले में यह संख्याओं का एक सरल सरणी है जैसा कि आप C प्रोग्राम में उपयोग कर सकते हैं। डी-अनुकूलित मामले में यह Valueवस्तुओं का एक सरणी है जो किसी भी प्रकार के मूल्यों के संदर्भों को पकड़ सकता है। (मैंने नाम बनाया Value, लेकिन मुद्दा यह है कि सरणी तत्व सिर्फ साधारण संख्या नहीं हैं, बल्कि ऐसी वस्तुएं हैं जो संख्याओं या अन्य प्रकारों को
माइकल गेरी

3
मैं V8 पर काम करता हूं। विचाराधीन सरणी को चिह्नित किया जाएगा HOLEYक्योंकि इसका उपयोग करके बनाया गया है new Array(n)(हालांकि कोड का यह हिस्सा ओपी में दिखाई नहीं दे रहा था)। V8 में HOLEYएरेज़ HOLEYहमेशा के लिए रहता है , तब भी जब वे बाद में भरे होते हैं। उस ने कहा, इस मामले में पूर्ण मुद्दे का कारण छिद्रपूर्ण नहीं है; इसका मतलब सिर्फ यह है कि हमें प्रत्येक पुनरावृत्ति (छेदों से बचाव के लिए) पर एक अतिरिक्त स्माइली जांच करनी है, जो कोई बड़ी बात नहीं है।
मैथियास ब्यनेंस 12

19

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. डेटा-प्रतिनिधित्व: मेमोरी में आंतरिक रूप से एरे को कैसे दर्शाया / संग्रहीत किया जाए (ऑब्जेक्ट, हैशमैप, 'वास्तविक' संख्यात्मक सरणी, आदि)
  2. फ़ंक्शनल मशीन-कोड: इन 'एरे' को एक्सेस / हैंडल (पढ़ने / संशोधित) करने वाले कोड को कैसे संकलित किया जाए

आइटम 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 विकल्प छोड़ता है:

  1. नंबर-क्रंचर के रूप में संकलित करें कि कोई आउट-ऑफ-बाउंड्स नहीं है, अंत में आउट-ऑफ-बाउंड्स समस्या में चलाएं, recompile और redo work (जैसा कि ऊपर 1 संपादित में सैद्धांतिक उदाहरण में उल्लिखित है)
  2. कंपाइलर पहले से ही पता चला है (या संदिग्ध?) बाउंड एसेस ऊपर-सामने और फ़ंक्शन जेआईटी-संकलित था जैसे कि तर्क पास हुआ एक स्पार्स ऑब्जेक्ट था जिसके परिणामस्वरूप धीमी कार्यात्मक मशीन-कोड होता है (क्योंकि इसमें अधिक चेक / बातचीत / ज़बरदस्ती होगी। आदि।)। दूसरे शब्दों में: फ़ंक्शन को कुछ ऑप्टिमाइज़ेशन के लिए कभी भी योग्य नहीं किया गया था, इसे संकलित किया गया था जैसे कि यह एक 'विरल सरणी' (- जैसे) तर्क प्राप्त करता है।

मुझे अब वास्तव में आश्चर्य है कि इन 2 में से कौन सा है!


2
कुछ अंतर्निहित मुद्दों पर एक अच्छी चर्चा - हालांकि आप मुश्किल से ही जवाब देते हैं (बहुत अंतिम वाक्य में)। हो सकता है कि बहुत ऊपर तक एक टीएल डालें? उदाहरण के लिए "धीमा लूप सीमा सीमा से अधिक होने के कारण है, जो इंजन को अनुकूलन के बिना लूप का पुनर्मूल्यांकन करने के लिए मजबूर करता है। जानने के लिए पढ़ें।"
ब्रिचिन्स

@brichins: धन्यवाद, और सुझाव के लिए धन्यवाद, जिसे मैंने अपने दूसरे अतिरिक्त संपादन के प्रकाश में थोड़ा सा पुन: प्रकाशित किया है, क्योंकि अब मैं इसके बारे में जितना सोचता हूं, शीर्ष पर यह कथन वास्तव में सही लगता है
GitaarLAB

6

इसमें कुछ वैज्ञानिकता जोड़ने के लिए, यहाँ एक jsperf है

https://jsperf.com/ints-values-in-out-of-array-bounds

यह एक सरणी के नियंत्रण मामले का परीक्षण करता है जो बाउंड्स के भीतर रहते हुए, मॉड्यूलर अंकगणित करते हुए ints और लूपिंग से भरा होता है। इसके 5 परीक्षण मामले हैं:

  • 1. सीमा से बाहर लूपिंग
  • 2. होली सरण
  • 3. NaNs के खिलाफ मॉड्यूलर अंकगणित
  • 4. पूरी तरह से अपरिभाषित मूल्य
  • 5. एक का उपयोग करना new Array()

यह दर्शाता है कि प्रदर्शन के लिए पहले 4 मामले वास्तव में खराब हैं । सीमा से बाहर लूपिंग अन्य 3 की तुलना में थोड़ा बेहतर है, लेकिन सभी 4 सर्वश्रेष्ठ मामले की तुलना में लगभग 98% धीमी हैं। मामले बस कुछ ही प्रतिशत धीमी लगभग कच्चे सरणी के रूप में अच्छा के रूप में है।
new Array()

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.