मुझे यह जानकर आश्चर्य हुआ कि जावास्क्रिप्ट ऑब्जेक्ट वास्तव में हुड के नीचे हैश मैप नहीं हैं, इसके बजाय वे अधिक समान हैं स्ट्रक्चर्स के लिए। जो मैं समझता हूं, किसी ऑब्जेक्ट पर गुण प्राप्त करना और सेट करना तेज़ है क्योंकि मान की स्मृति स्थान एक निश्चित ऑफ़सेट पर है, क्योंकि यह एक संरचना या कक्षा में होगा। जो मुझे समझ में नहीं आता है वह यह है कि सिंटैक्स उस निश्चित ऑफ़सेट को कैसे मैप करता है। यानी क्या होता है जब कंपाइलर obj.a या obj[‘a’] देखता है। क्या वह वाक्यविन्यास रन टाइम या संकलन समय या जेआईटी पर एक पूर्णांक ऑफसेट में परिवर्तित हो गया है? मुझे लगता है कि मैं जो समझने की कोशिश कर रहा हूं वह यह है कि यह आने वाली स्ट्रिंग 'ए' को index = hash(‘a’) % objectLength जैसा कुछ किए बिना कुशलतापूर्वक एक पूर्णांक सूचकांक में कैसे बदल सकता है।

हो सकता है कि मेरे ज्ञान में अंतर यह है कि मैं पूरी तरह से नहीं जानता कि संकलक स्तर पर संरचना कैसे काम करती है।

2
david_adler 29 जिंदा 2021, 02:15

2 जवाब

सबसे बढ़िया उत्तर

(वी 8 डेवलपर यहां।)

जावास्क्रिप्ट ऑब्जेक्ट वास्तव में हुड के नीचे हैश मैप नहीं हैं, इसके बजाय वे स्ट्रक्चर के समान हैं।

रिकॉर्ड के लिए, बर्गी सही ढंग से बताते हैं कि यह एक इंजन में सच है, और उस इंजन में भी हमेशा नहीं। जावास्क्रिप्ट इंजनों को आंतरिक रूप से वस्तुओं का प्रतिनिधित्व करने के लिए बहुत अधिक स्वतंत्रता है, और वे उस स्वतंत्रता का उपयोग करते हैं।

जो मुझे समझ में नहीं आता है वह यह है कि सिंटैक्स उस निश्चित ऑफ़सेट को कैसे मैप करता है। यानी क्या होता है जब कंपाइलर obj.a या obj['a'] देखता है। क्या वह वाक्यविन्यास रन टाइम या संकलन समय या जेआईटी पर एक पूर्णांक ऑफसेट में परिवर्तित हो गया है?

सिस्टम कैशिंग, और "हिडन क्लासेस" (कभी-कभी "ऑब्जेक्ट शेप्स" या "[ऑब्जेक्ट] शेप डिस्क्रिप्टर" के रूप में संदर्भित) पर आधारित है।

जब आपके पास कोई ऑब्जेक्ट obj = {a: 42, b: "hello", c: null} होगा, तो इसमें एक छिपी हुई कक्षा होगी (चलिए इसे hiddenClassA कहते हैं जो सभी गुणों और उनके ऑफ़सेट को सूचीबद्ध करता है, उदाहरण के लिए "संपत्ति a ऑफ़सेट 12 पर संग्रहीत है"।

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

यदि फ़ंक्शन पर्याप्त गर्म चलता है, तो यह अंततः अनुकूलित हो जाएगा। ऑप्टिमाइज़िंग कंपाइलर छिपे हुए वर्गों और ऑफ़सेट को देखता है जो कि अडॉप्टिमाइज्ड कोड ने कैश किया है, और मानता है कि आपके ऐप का भविष्य का व्यवहार पिछले व्यवहार की तरह ही होगा, इसलिए यह एक कोड अनुक्रम का उत्सर्जन करेगा जैसे:

  1. सत्यापित करें कि obj में छिपी हुई कक्षा hiddenClassA है, अन्यथा deoptimize
  2. ऑफसेट से लोड 12

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

यह तंत्र इसलिए भी है कि अनुकूलित कोड को तुरंत संकलित करने का कोई मतलब नहीं होगा: संपत्ति के उपयोग जैसी चीजों को यथोचित रूप से अनुकूलित नहीं किया जा सकता है जब अनुकूलन संकलक के पास कोई कैश्ड प्रकार की जानकारी उपलब्ध नहीं होती है (अडॉप्टिमाइज्ड निष्पादन द्वारा उत्पन्न)। क्योंकि तब अनुकूलन करने वाला संकलक ठीक वही प्रश्न पूछेगा जो आपने किया था: "पृथ्वी पर कैसे मुझे यह पता लगाना चाहिए कि ऑफसेट संपत्ति a के नक्शे ???"

3
jmrk 29 जिंदा 2021, 03:20

मुझे लगता है कि यह एक अत्यधिक सरलीकृत उदाहरण को देखने में मदद करता है कि कोई वस्तु स्मृति में कैसी दिख सकती है:

 { a: 1, b: 2 }
 // represented as
 address | 0  | 1  | 2  |  3   | 4  | 5  |
 value   | 3  | 1  | 2  |  a   | b  |    |

ऑब्जेक्ट मान पते 0 पर संग्रहीत किया जाता है। पहला मान 3 "छिपे हुए वर्ग" को इंगित करता है। 0 पर ऑब्जेक्ट की कुंजी "ए" के मान को पुनः प्राप्त करने के लिए, कोई 0 पढ़ेगा, फिर 0 में संग्रहीत पते पर मान पढ़ेगा, और फिर गिनें और उस पते के मान को तब तक देखें जब तक हमें "ए" न मिल जाए। सौभाग्य से यह पहली कुंजी है, इसलिए हम फिर ऑब्जेक्ट पर वापस जा सकते हैं, और ऑफसेट 1 को हमारे एड्रेस 0 में जोड़ सकते हैं, और 0 + 1 पर मान ढूंढ सकते हैं। या स्यूडोकोड (सी ++) में:

 void* obj = 0;
 void* hidden_class = *obj;
 int offset = 0;
 while(*(hidden_class + offset) != 'a') offset++;
 int value = *(obj + offset + 1);

अब अगर हमारे पास इस तरह दिखने वाली कोई अन्य वस्तु है:

 address | 100  | 101  | 102 |
 value   |   3  |   5  |   7 |

तब हम ऊपर के रूप में उसी विधि का उपयोग कर सकते थे, या यदि हम जगह थे obj.a हमेशा उस छिपे हुए वर्ग के ऑब्जेक्ट्स पास हो जाते हैं, तो हम यह भी कर सकते हैं:

  void* obj = 100;
  assert(*obj == 3);
  int value = *(obj + 1);

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

उपरोक्त उदाहरण निश्चित रूप से बहुत सरल है (प्रत्येक मान एक बाइट में फिट नहीं होता है)।

1
Jonas Wilms 29 जिंदा 2021, 03:22