जावास्क्रिप्ट में ऑब्जेक्ट्स / एरेस का प्रदर्शन क्या है? (विशेष रूप से Google V8 के लिए)


105

जावास्क्रिप्ट में Arrays और ऑब्जेक्ट्स (विशेष रूप से Google V8) से जुड़ा प्रदर्शन दस्तावेज़ के लिए बहुत दिलचस्प होगा। मुझे इंटरनेट पर कहीं भी इस विषय पर कोई व्यापक लेख नहीं मिला।

मैं समझता हूं कि कुछ ऑब्जेक्ट्स अपने अंतर्निहित डेटा संरचना के रूप में कक्षाओं का उपयोग करते हैं। यदि बहुत सारे गुण हैं, तो इसे कभी-कभी हैश तालिका के रूप में माना जाता है?

मैं यह भी समझता हूं कि कभी-कभी Arrays को C ++ Arrays (यानी तेजी से यादृच्छिक अनुक्रमण, धीमी गति से विलोपन और आकार बदलने) के समान माना जाता है। और, अन्य समय में, उन्हें वस्तुओं की तरह अधिक व्यवहार किया जाता है (तेजी से अनुक्रमण, तेज सम्मिलन / हटाने, अधिक स्मृति)। और, शायद कभी-कभी उन्हें लिंक की गई सूचियों के रूप में संग्रहीत किया जाता है (यानी शुरुआत / अंत में धीमी यादृच्छिक अनुक्रमण, तेज निष्कासन / सम्मिलन)

जावास्क्रिप्ट में एरे / ऑब्जेक्ट पुनर्प्राप्ति और जोड़तोड़ का सटीक प्रदर्शन क्या है? (विशेष रूप से Google V8 के लिए)

अधिक विशेष रूप से, इसका प्रदर्शन प्रभाव क्या है:

  • किसी वस्तु में गुण जोड़ना
  • किसी वस्तु से संपत्ति निकालना
  • किसी वस्तु में संपत्ति का अनुक्रमण करना
  • किसी ऐरे में आइटम जोड़ना
  • किसी ऐरे से कोई आइटम निकालना
  • किसी ऐरे में किसी वस्तु का अनुक्रमण करना
  • कॉलिंग Array.pop ()
  • कॉलिंग Array.push ()
  • कॉलिंग Array.shift ()
  • कॉलिंग Array.unshift ()
  • कॉलिंग Array.slice ()

अधिक जानकारी के लिए किसी भी लेख या लिंक की सराहना की जाएगी, साथ ही साथ। :)

संपादित करें: मैं वास्तव में सोच रहा हूं कि कैसे जावास्क्रिप्ट सरणियों और ऑब्जेक्ट्स हुड के नीचे काम करते हैं। इसके अलावा, V8 इंजन किस संदर्भ में "स्विच-ओवर" को किसी अन्य डेटा संरचना में जानता है?

उदाहरण के लिए, मान लीजिए कि मैं एक सरणी बनाता हूं ...

var arr = [];
arr[10000000] = 20;
arr.push(21);

यहाँ वास्तव में क्या हो रहा है?

या ... इस बारे में क्या ... ???

var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
    arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
    var item = arr[i].shift();
    //Do something with item...
}

पारंपरिक सरणियों के लिए, प्रदर्शन भयानक होगा; जबकि, अगर एक लिंक्डलिस्ट का इस्तेमाल किया गया ... इतना बुरा नहीं।


2
Jsperf.com पर जाएं , और टेस्ट केस बनाएं।
रॉब डब्ल्यू

2
@RobW यहाँ पर खेलने की तुलना में सरल परीक्षणों की तुलना में अधिक है, जिसके लिए यह आवश्यक है कि JIT कंपाइलर कैसे काम करता है और डेटा के साथ क्या किया जा रहा है। अगर मुझे कुछ समय लगता है तो मैं एक उत्तर जोड़ूंगा, लेकिन उम्मीद है कि किसी और के पास समय के साथ किरकिरा होने का समय होगा। इसके अलावा, मैं इस लिंक को यहाँ छोड़ना चाहूंगा
Incognito

मैं जिन चीजों के बारे में बात कर रहा हूं, वे एक वस्तु के "आकार" जैसी चीजें हैं, या परिभाषित तत्वों के बीच अपरिभाषित मूल्यों के साथ सरणियां, साथ ही साथ हाल ही में टाइप-विशेषज्ञता वाली विशेषताओं के साथ प्रयोग किए गए ... सरणी-विशिष्ट तरीके उपयोग के रूप में निर्भर हो सकते हैं साथ ही अगर प्रोटोटाइप में हेरफेर किया गया है या नहीं। किसी अन्य डेटा प्रकार AFAIK पर स्विच करने के लिए "जानने" जैसी कोई चीज नहीं है।
इनकॉगनिटो

1
Google के प्रतिनिधि चर्चा कर रहे हैं कि विभिन्न ऑप्टिमाइज़र और आंतरिक सिस्टम कैसे काम करते हैं। और उनके लिए अनुकूलन कैसे करें। (गेम्स के लिए!) youtube.com/watch?v=XAqIpGU8ZZk
PicoCreator

जवाबों:


279

मैंने इन मुद्दों (और अधिक) ( संग्रहीत प्रति ) का पता लगाने के लिए एक परीक्षण सूट बनाया ।

और उस अर्थ में, आप इस 50+ परीक्षण केस परीक्षक में प्रदर्शन के मुद्दों को देख सकते हैं (इसमें लंबा समय लगेगा)।

जैसा कि इसके नाम से ही पता चलता है, यह डोम संरचना की मूल लिंक्ड सूची प्रकृति का उपयोग करने की खोज करता है।

(वर्तमान में, प्रगति पर फिर से बनाया गया है) इस बारे में मेरे ब्लॉग पर अधिक जानकारी

सारांश इस प्रकार है

  • V8 एरे फास्ट, बहुत तेज़ है
  • ऐरे पुश / पॉप / शिफ्ट ~ लगभग 20x + किसी भी ऑब्जेक्ट के बराबर तेज है।
  • हैरानी की बात है Array.shift() से एक सरणी पॉप की तुलना में तेज ~ लगभग 6x धीमा है, लेकिन एक वस्तु विशेषता विलोपन से ~ लगभग 100x तेज है।
  • कुल मिलाकर, लगभग 20 (डायनेमिक ऐरे) से 10 गुना (निश्चित एरे) समय से अधिक Array.push( data );तेज़ है Array[nextIndex] = data
  • Array.unshift(data) उम्मीद के मुताबिक धीमी है, और एक नई संपत्ति जोड़ने की तुलना में ~ लगभग 5x धीमी है।
  • मान array[index] = nullको खींचना इसे delete array[index](अपरिभाषित) से एक सरणी में ~ ~ लगभग 4x ++ अधिक तेजी से हटाने से तेज है।
  • आश्चर्यजनक रूप से एक वस्तु में एक मूल्य को खींचना obj[attr] = null~ लगभग 2x धीमी है केवल विशेषता को हटाने की तुलना मेंdelete obj[attr]
  • अप्रत्याशित रूप से, मध्य सरणी Array.splice(index,0,data)धीमी, बहुत धीमी है।
  • हैरानी की बात है, Array.splice(index,1,data)अनुकूलित किया गया है (लंबाई में कोई बदलाव नहीं) और सिर्फ ब्याह से 100 गुना तेज हैArray.splice(index,0,data)
  • दुर्भाग्य से, divLinkedList सभी क्षेत्रों पर एक सरणी से नीच है, सिवाय dll.splice(index,1)हटाने के (जहां इसने परीक्षा को तोड़ दिया)।
  • सबसे बड़ा आश्चर्य यह सब के रूप में [jjrv ने बताया], वी 8 सरणी राईट थोड़ा तेजी से वी 8 पढ़ता हैं = हे

नोट: ये मैट्रिक्स केवल बड़े सरणी / ऑब्जेक्ट्स पर लागू होते हैं जो v8 "पूरी तरह से ऑप्टिमाइज़ आउट" नहीं करता है। सरणी / ऑब्जेक्ट आकार के लिए बहुत अलग-थलग अनुकूलित अनुकूलित मामले हो सकते हैं फिर एक मनमाना आकार (24?)। कई विवरणों को कई Google IO वीडियो में बड़े पैमाने पर देखा जा सकता है।

नोट 2: ये अद्भुत प्रदर्शन परिणाम ब्राउज़रों में साझा नहीं किए जाते हैं, विशेष रूप से *cough*IE। इसके अलावा परीक्षण बहुत बड़ा है, इसलिए मुझे अभी तक परिणामों का पूरी तरह से विश्लेषण और मूल्यांकन करना है: कृपया इसे संपादित करें =)

अपडेटेड नोट (2012 से): Google प्रतिनिधियों के पास क्रोम के आंतरिक कामकाज का वर्णन करने वाले youtubes पर वीडियो हैं (जैसे कि जब यह एक लिंक्डइन सरणी से एक निश्चित सरणी पर स्विच करता है, आदि), और उन्हें कैसे अनुकूलित करें। GDC 2012 देखें : कंसोल से क्रोम के लिए और अधिक।


2
उनमें से कुछ परिणाम बहुत अजीब लगते हैं। उदाहरण के लिए क्रोम ऐरे में लिखते हैं कि रीड की तुलना में लगभग 10x तेज है, जबकि फ़ायरफ़ॉक्स में इसके विपरीत है। क्या आप सुनिश्चित हैं कि ब्राउज़र JIT कुछ मामलों में आपके पूरे परीक्षण का अनुकूलन नहीं कर रहा है?
jjrv

1
@jjrv अच्छा gosh = O आप सही हैं ... मैंने JIT को रोकने के लिए प्रत्येक लिखने के मामले को वृद्धिशील रूप से अनूठे रूप से अपडेट किया है ... और ईमानदारी से, जब तक कि JIT ऑप्टिमाइज़ेशन अच्छा न हो (जो मुझे विश्वास करना मुश्किल है) यह सिर्फ खराब तरीके से अनुकूलित पढ़ा हुआ, या भारी अनुकूलित लिखा जा सकता है (तत्काल बफर को लिखें?) ... जो जांच के लायक है: lol
PicoCreator

2
बस सरणियों पर वीडियो चर्चा में सटीक बिंदु जोड़ना चाहते थे: youtube.com/…
बदनाम

1
JsPerf साइट अब मौजूद नहीं है :(
जस्टगोसा

1
@JustGoscha ठीक है, इस जानकारी के लिए thx: मैं इसे गूगल कैश से पुनः बनाने के द्वारा वापस तय कर दिया।
पिकोक्रिएट

5

एक मूल स्तर पर जो जावास्क्रिप्ट के दायरे में रहता है, वस्तुओं पर गुण बहुत अधिक जटिल हैं। आप अलग-अलग enumerability, writability, और विन्यास के साथ बसने / पाने वालों के साथ गुण बना सकते हैं। किसी सरणी में कोई आइटम इस तरह से अनुकूलित नहीं किया जा सकता है: यह या तो मौजूद है या यह नहीं है। अंतर्निहित इंजन स्तर पर यह संरचना को दर्शाने वाली मेमोरी को व्यवस्थित करने के संदर्भ में बहुत अधिक अनुकूलन की अनुमति देता है।

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

गुणों को एक सरणी ऑब्जेक्ट पर संग्रहीत किया जा सकता है लेकिन यह केवल यह दर्शाता है कि जावास्क्रिप्ट कैसे सब कुछ एक वस्तु बनाने पर जोर देता है। किसी सरणी में अनुक्रमित मान किसी भी गुण से अलग संग्रहीत किए जाते हैं, जो आप उस सरणी ऑब्जेक्ट पर सेट करने का निर्णय लेते हैं जो अंतर्निहित सरणी डेटा का प्रतिनिधित्व करता है।

जब भी आप एक कानूनी सरणी ऑब्जेक्ट का उपयोग कर रहे हैं और उस सरणी को हेरफेर करने के मानक तरीकों में से एक का उपयोग कर रहे हैं, तो आप अंतर्निहित सरणी डेटा को हिट करने जा रहे हैं। V8 में विशेष रूप से, ये अनिवार्य रूप से C ++ सरणी के समान हैं, इसलिए ये नियम लागू होंगे। यदि किसी कारण से आप एक ऐसे सरणी के साथ काम कर रहे हैं जो इंजन विश्वास के साथ निर्धारित करने में सक्षम नहीं है तो एक सरणी है, तो आप बहुत शकीर मैदान पर हैं। V8 के हाल के संस्करणों के साथ हालांकि काम करने के लिए अधिक जगह है। उदाहरण के लिए, एक ऐसा वर्ग बनाना संभव है, जिसमें इसके प्रोटोटाइप के रूप में Array.prototype है और अभी भी विभिन्न देशी सरणी हेरफेर विधियों के लिए कुशल पहुंच प्राप्त करता है। लेकिन यह हालिया बदलाव है।

सरणी में हेरफेर के हाल के परिवर्तनों के लिए विशिष्ट लिंक यहाँ काम आ सकते हैं:

कुछ अतिरिक्त के रूप में, यहाँ V8 के स्रोत से सीधे ऐरे पॉप और ऐरे पुश, दोनों ही JS में लागू किए गए हैं:

function ArrayPop() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.pop"]);
  }

  var n = TO_UINT32(this.length);
  if (n == 0) {
    this.length = n;
    return;
  }
  n--;
  var value = this[n];
  this.length = n;
  delete this[n];
  return value;
}


function ArrayPush() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.push"]);
  }

  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();
  for (var i = 0; i < m; i++) {
    this[i+n] = %_Arguments(i);
  }
  this.length = n + m;
  return this.length;
}

1

मैं इस सवाल के जांच के साथ मौजूदा उत्तरों को पूरक करना चाहता हूं कि बढ़ती सरणियों के बारे में कैसे व्यवहार किया जाता है: यदि वे उन्हें "सामान्य" तरीके से लागू करते हैं, तो किसी को दुर्लभ के साथ कई त्वरित धक्का दिखाई देंगे, धीमी गति से धक्का जिससे कार्यान्वयन प्रतियां प्रभावित होती हैं सरणी का आंतरिक प्रतिनिधित्व एक बफ़र से बड़े एक तक।

आप इस प्रभाव को बहुत अच्छी तरह से देख सकते हैं, यह क्रोम से है:

16: 4ms
40: 8ms 2.5
76: 20ms 1.9
130: 31ms 1.7105263157894737
211: 14ms 1.623076923076923
332: 55ms 1.5734597156398105
514: 44ms 1.5481927710843373
787: 61ms 1.5311284046692606
1196: 138ms 1.5196950444726811
1810: 139ms 1.5133779264214047
2731: 299ms 1.5088397790055248
4112: 341ms 1.5056755767118273
6184: 681ms 1.5038910505836576
9292: 1324ms 1.5025873221216042

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

तो पहली संख्या यह दर्शाती है कि कौन सा तत्व डाला गया है (पहली पंक्ति 17 वें तत्व के लिए है), दूसरी यह है कि इसे कितनी देर लगी (कई सरणियों के लिए बेंचमार्क समानांतर में किया जाता है), और अंतिम मान विभाजन है पूर्व पंक्ति में एक के द्वारा पहला नंबर।

Chrome के लिए 2ms से कम निष्पादन समय वाली सभी लाइनें शामिल नहीं हैं।

आप देख सकते हैं कि क्रोम 1.5 की शक्तियों में सरणी आकार को बढ़ाता है, साथ ही कुछ सरणियों को छोटे सरणियों के लिए खाता है।

फ़ायरफ़ॉक्स के लिए, यह दो की शक्ति है:

126: 284ms
254: 65ms 2.015873015873016
510: 28ms 2.0078740157480315
1022: 58ms 2.003921568627451
2046: 89ms 2.0019569471624266
4094: 191ms 2.0009775171065494
8190: 364ms 2.0004885197850513

मुझे फ़ायरफ़ॉक्स में थ्रेशोल्ड को थोड़ा ऊपर रखना था, इसलिए हम # 126 पर शुरू करते हैं।

IE के साथ, हमें एक मिश्रण मिलता है:

256: 11ms 256
512: 26ms 2
1024: 77ms 2
1708: 113ms 1.66796875
2848: 154ms 1.6674473067915691
4748: 423ms 1.6671348314606742
7916: 944ms 1.6672283066554338

यह पहले दो की शक्ति है और फिर यह पाँच तिहाई की शक्तियों की ओर बढ़ती है।

इसलिए सभी सामान्य कार्यान्वयन सरणियों के लिए "सामान्य" तरीके का उपयोग करते हैं ( उदाहरण के लिए रस्सियों के साथ पागल होने के बजाय )।

यहाँ बेंचमार्क कोड है और यहाँ पर यह फिडेल है।

var arrayCount = 10000;

var dynamicArrays = [];

for(var j=0;j<arrayCount;j++)
    dynamicArrays[j] = [];

var lastLongI = 1;

for(var i=0;i<10000;i++)
{
    var before = Date.now();
    for(var j=0;j<arrayCount;j++)
        dynamicArrays[j][i] = i;
    var span = Date.now() - before;
    if (span > 10)
    {
      console.log(i + ": " + span + "ms" + " " + (i / lastLongI));
      lastLongI = i;
    }
}

0

नोड.जेएस 0.10 (वी 8 पर निर्मित) के तहत चलने के दौरान मैं सीपीयू उपयोग देख रहा था जो कार्यभार के लिए अत्यधिक लग रहा था। मैंने एक फ़ंक्शन में एक प्रदर्शन समस्या का पता लगाया जो एक सरणी में स्ट्रिंग के अस्तित्व के लिए जाँच कर रहा था। इसलिए मैंने कुछ परीक्षण किए।

  • 90,822 मेजबानों को लोड किया
  • लोडिंग कॉन्फिग को 0.087 सेकंड (सरणी) लिया गया
  • लोडिंग कॉन्फिग 0.152 सेकंड (ऑब्जेक्ट) लिया गया

किसी सरणी (मान्य और पुश के साथ) में 91k प्रविष्टियां लोड करना obj [कुंजी] = मान सेट करने से अधिक तेज़ है।

अगले परीक्षण में, मैंने प्रत्येक होस्टनाम को सूची में एक बार देखा (91k पुनरावृत्तियों, औसत समय देखने के लिए):

  • खोज कॉन्फ़िगरेशन में 87.56 सेकंड (सरणी) लिया गया
  • खोज कॉन्फ़िगरेशन में 0.21 सेकंड (ऑब्जेक्ट) लिया गया

यहाँ एप्लिकेशन हरका (एक एसएमटीपी सर्वर) है और यह एक बार स्टार्टअप (और बदलाव के बाद) पर host_list लोड करता है और बाद में ऑपरेशन के दौरान लाखों बार यह लुकअप करता है। किसी ऑब्जेक्ट पर स्विच करना एक बहुत बड़ी प्रदर्शन जीत थी।

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