जावास्क्रिप्ट में कचरा कलेक्टर गतिविधि को कम करने के लिए सर्वोत्तम अभ्यास


94

मेरे पास एक काफी जटिल जावास्क्रिप्ट ऐप है, जिसमें एक मुख्य लूप है जिसे प्रति सेकंड 60 बार कहा जाता है। ऐसा लगता है कि बहुत अधिक कचरा संग्रह हो रहा है (क्रोम देव उपकरणों में मेमोरी टाइमलाइन से 'sawtooth' आउटपुट के आधार पर) - और यह अक्सर एप्लिकेशन के प्रदर्शन को प्रभावित करता है।

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

ऐप को जॉन रेजिग के सिंपल जावास्क्रिप्ट इनहेरिटेंस की तर्ज पर 'क्लासेस' में संरचित किया गया है ।

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

मैं बड़ी / भारी वस्तुओं के लिए ऑब्जेक्ट पूलिंग के बारे में जानता हूं (और हम इसे एक हद तक उपयोग करते हैं), लेकिन मैं उन तकनीकों की तलाश कर रहा हूं जो बोर्ड भर में लागू हो सकते हैं, विशेष रूप से उन कार्यों से संबंधित हैं जिन्हें तंग छोरों में बहुत बार कहा जाता है ।

कचरा बीनने वाले को काम की मात्रा कम करने के लिए मैं किन तकनीकों का उपयोग कर सकता हूं?

और, शायद यह भी - कि किन वस्तुओं को कचरा एकत्र करने के लिए सबसे अधिक एकत्र किया जा रहा है, इसकी पहचान करने के लिए कौन सी तकनीक नियोजित की जा सकती है? (यह एक बहुत बड़ा कोडबेस है, इसलिए ढेर के स्नैपशॉट की तुलना बहुत उपयोगी नहीं है)


2
क्या आपके पास अपने कोड का एक उदाहरण है जो आप हमें दिखा सकते हैं? सवाल का जवाब देना आसान होगा (लेकिन संभावित रूप से कम सामान्य भी है, इसलिए मुझे यहाँ यकीन नहीं है)
जॉन ड्वोरक

2
प्रति सेकंड हजारों बार चलने वाले कार्यों को कैसे रोकें? क्या वास्तव में यह दृष्टिकोण करने का एकमात्र तरीका है? यह प्रश्न एक XY समस्या जैसा लगता है। आप एक्स का वर्णन कर रहे हैं, लेकिन आप वास्तव में क्या देख रहे हैं वाई के लिए एक समाधान है
ट्रैविस जे

2
@TravisJ: वह इसे प्रति सेकंड केवल 60 बार चलाता है, जो काफी सामान्य एनीमेशन दर है। वह कम काम करने के लिए नहीं कहता है, लेकिन इसे अधिक कचरा-संग्रह-कुशल कैसे करना है।
बरगी

1
@ बर्गी - "कुछ कार्यों को प्रति सेकंड हजारों बार कहा जा सकता है"। यह एक बार प्रति मिलीसेकंड (संभवतः बदतर!) है। यह बिल्कुल भी सामान्य नहीं है। प्रति सेकंड 60 बार एक मुद्दा नहीं होना चाहिए। यह प्रश्न अत्यधिक अस्पष्ट है और केवल राय या अनुमान उत्पन्न करने वाला है।
ट्रैविस जे

4
@TravisJ - यह खेल चौखटे में असामान्य नहीं है।
UpTheCreek

जवाबों:


127

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

आवंटन कई स्थानों पर आधुनिक दुभाषियों में होता है:

  1. जब आप newशाब्दिक वाक्य रचना के माध्यम से या के माध्यम से एक वस्तु बनाते हैं [...], या {}
  2. जब आप तार काटते हैं।
  3. जब आप एक कार्यक्षेत्र घोषित करते हैं जिसमें फ़ंक्शन घोषणाएं होती हैं।
  4. जब आप एक कार्रवाई करते हैं जो एक अपवाद को ट्रिगर करता है।
  5. जब आप एक फ़ंक्शन अभिव्यक्ति का मूल्यांकन करते हैं (function (...) { ... }):।
  6. जब आप एक ऑपरेशन करते हैं, जो ऑब्जेक्ट की तरह Object(myNumber)या करने के लिए मजबूर करता हैNumber.prototype.toString.call(42)
  7. जब आप एक बिल्डिन को कॉल करते हैं जो हुड के नीचे इनमें से कोई भी करता है, जैसे Array.prototype.slice
  8. जब आप argumentsपैरामीटर सूची पर प्रतिबिंबित करने के लिए उपयोग करते हैं।
  9. जब आप एक स्ट्रिंग को विभाजित करते हैं या एक नियमित अभिव्यक्ति के साथ मेल खाते हैं।

उन करने से बचें, और पूल और जहाँ संभव हो, वस्तुओं का पुन: उपयोग करें।

विशेष रूप से, अवसरों के लिए बाहर देखो:

  1. आंतरिक कार्यों को रोकें जिनकी बंद या अधिक निर्भरता वाले राज्य उच्च, लंबे समय तक रहने वाले दायरे में हैं। (कुछ कोड मिनिज़र जैसे क्लोजर कंपाइलर इनर फंक्शन्स को इनलाइन कर सकते हैं और आपके जीसी प्रदर्शन को बेहतर बना सकते हैं।)
  2. स्ट्रक्चर्ड डेटा या डायनामिक एड्रेसिंग का प्रतिनिधित्व करने के लिए स्ट्रिंग्स का उपयोग करने से बचें। विशेष रूप से बार-बार पार्सिंग का उपयोग करने से बचें splitया नियमित अभिव्यक्ति से मेल खाता है क्योंकि प्रत्येक को कई ऑब्जेक्ट आवंटन की आवश्यकता होती है। यह अक्सर लुकअप टेबल और डायनेमिक DOM नोड आईडी में कुंजियों के साथ होता है। उदाहरण के लिए, lookupTable['foo-' + x]और document.getElementById('foo-' + x)दोनों में एक आवंटन शामिल है क्योंकि एक स्ट्रिंग संघनन है। अक्सर आप लंबे समय तक जीवित वस्तुओं के लिए फिर से कंक्रीटिंग के बजाय चाबियाँ संलग्न कर सकते हैं। आपके द्वारा समर्थित ब्राउज़रों के आधार पर, आप Mapसीधे कुंजी के रूप में वस्तुओं का उपयोग करने में सक्षम हो सकते हैं।
  3. सामान्य कोड-पाथ पर अपवादों को पकड़ने से बचें। इसके बजाय try { op(x) } catch (e) { ... }, करो if (!opCouldFailOn(x)) { op(x); } else { ... }
  4. जब आप स्ट्रिंग्स बनाने से बच नहीं सकते हैं, उदाहरण के लिए किसी सर्वर को संदेश भेजने के लिए, एक अंतर्निहित का उपयोग करें जैसे JSON.stringifyकि एक आंतरिक देशी बफर का उपयोग करके कई वस्तुओं को आवंटित करने के बजाय सामग्री जमा करने के लिए।
  5. उच्च-आवृत्ति घटनाओं के लिए कॉलबैक का उपयोग करने से बचें, और जहां आप कर सकते हैं, कॉलबैक के रूप में एक लंबे समय तक चलने वाले फ़ंक्शन (1 देखें) को पास करें जो संदेश सामग्री से राज्य को फिर से बनाता है।
  6. उन argumentsकार्यों का उपयोग करने से बचें, जिनका उपयोग तब किया जाता है जब कहा जाता है कि एक सरणी जैसी ऑब्जेक्ट बनाना है।

मैंने JSON.stringifyनिवर्तमान नेटवर्क संदेश बनाने के लिए उपयोग करने का सुझाव दिया । JSON.parseस्पष्ट रूप से उपयोग करने वाले इनपुट संदेशों को पार्स करने में आवंटन और बड़े संदेशों के लिए बहुत सारे शामिल हैं। यदि आप अपने आने वाले संदेशों को आदिम के सरणियों के रूप में दर्शा सकते हैं, तो आप बहुत सारे आवंटन बचा सकते हैं। एकमात्र अन्य बिलिन जिसके चारों ओर आप एक पार्सर बना सकते हैं जो आवंटित नहीं करता है String.prototype.charCodeAt। एक जटिल प्रारूप के लिए एक पार्सर जो केवल उसी का उपयोग करता है जो हालांकि पढ़ने के लिए नारकीय होने वाला है।


आपको नहीं लगता कि JSON.parsed ऑब्जेक्ट्स संदेश स्ट्रिंग की तुलना में कम (या बराबर) स्थान आवंटित करते हैं?
बेर्गी

@ बर्गी, यह इस बात पर निर्भर करता है कि क्या संपत्ति के नाम के लिए अलग-अलग आवंटन की आवश्यकता होती है, लेकिन एक पार्सर जो पार्स ट्री के बजाय घटनाओं को उत्पन्न करता है, कोई बाहरी एलोकाइटन नहीं करता है।
माइक सैमुअल

शानदार जवाब, धन्यवाद! इनाम की अवधि समाप्त होने के लिए कई माफी - मैं उस समय यात्रा कर रहा था, और किसी कारण से मैं अपने फोन पर अपने जीमेल खाते के साथ एसओ में प्रवेश नहीं कर सका। ....:
उप-

इनाम के साथ अपने बुरे समय के लिए बनाने के लिए मैंने इसे ऊपर करने के लिए एक अतिरिक्त जोड़ा है (200 मैं जो न्यूनतम दे सकता था?) - किसी कारण से हालांकि मुझे पुरस्कार देने से पहले 24 घंटे इंतजार करने की आवश्यकता होती है (हालांकि मैंने 'इनाम मौजूदा जवाब' का चयन किया)। कल तुम्हारा होगा ...
UpTheCric

@UpTheCreek, कोई चिंता नहीं। मुझे खुशी है कि आपने इसे उपयोगी पाया।
माइक सैमुअल

13

Chrome डेवलपर टूल स्मृति आवंटन अनुरेखण के लिए एक बहुत अच्छा सुविधा है। इसे मेमोरी टाइमलाइन कहा जाता है। यह लेख कुछ विवरणों का वर्णन करता है। मुझे लगता है कि यह वही है जो आप "आरा" के बारे में बात कर रहे हैं? यह अधिकांश GC'ed रनटाइम्स के लिए सामान्य व्यवहार है। आवंटन तब तक बढ़ता है जब तक कि उपयोग सीमा एक संग्रह को ट्रिगर करने तक नहीं पहुंच जाती है। आम तौर पर विभिन्न थ्रेसहोल्ड पर विभिन्न प्रकार के संग्रह होते हैं।

क्रोम में मेमोरी टाइमलाइन

कचरा संग्रह उनकी अवधि के साथ ट्रेस के साथ जुड़े इवेंट सूची में शामिल हैं। मेरे बजाय पुराने नोटबुद्धि पर, अल्पकालिक संग्रह लगभग 4Mb पर हो रहा है और 30ms ले रहा है। यह आपके 60Hz लूप पुनरावृत्तियों में से 2 है। यदि यह एक एनीमेशन है, तो 30ms संग्रह संभवतः हकलाने का कारण बन रहे हैं। आपको यहां यह देखना चाहिए कि आपके वातावरण में क्या चल रहा है: संग्रह की सीमा कहां है और आपके संग्रह में कितना समय लग रहा है। यह आपको अनुकूलन का आकलन करने के लिए एक संदर्भ बिंदु देता है। लेकिन आप शायद आवंटन दर को धीमा करके, संग्रह के बीच अंतराल को लंबा करके हकलाने की आवृत्ति को कम करने से बेहतर नहीं करेंगे।

अगला चरण प्रोफाइल का उपयोग करना है | रिकॉर्ड प्रकार से आवंटन की सूची तैयार करने के लिए रिकॉर्ड हीप आवंटन की सुविधा है। यह जल्दी से दिखाएगा कि ट्रेस अवधि के दौरान कौन से ऑब्जेक्ट प्रकार सबसे अधिक मेमोरी का उपभोग कर रहे हैं, जो आवंटन दर के बराबर है। दर के अवरोही क्रम में इन पर ध्यान केंद्रित करें।

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

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


सही है, यही मेरा मतलब है आरा से। मुझे पता है कि हमेशा किसी न किसी प्रकार का एक आरा पैटर्न होगा, लेकिन मेरी चिंता यह है कि मेरे ऐप में sawtooth आवृत्ति और 'चट्टान' काफी अधिक हैं। दिलचस्प बात यह है जी सी घटनाओं मेरे समय रेखा पर दिखाई नहीं देती हैं - केवल उन्हीं इवेंट 'रिकॉर्ड' फलक (मध्य एक) में प्रदर्शित कर रहे हैं: request animation frame, animation frame fired, और composite layers। मुझे नहीं पता कि मैं ऐसा क्यों नहीं देख रहा हूं GC Eventजैसे आप हैं (यह क्रोम के नवीनतम संस्करण पर है, और कैनरी भी है)।
UpTheCreek

4
मैंने 'रिकॉर्ड हीप एलोकेशन' के साथ प्रोफाइलर का उपयोग करने की कोशिश की है, लेकिन अभी तक इसे बहुत उपयोगी नहीं पाया है। शायद यह इसलिए है क्योंकि मुझे नहीं पता कि इसका सही इस्तेमाल कैसे किया जाए। यह उन संदर्भों से भरा हुआ प्रतीत होता है जिनका अर्थ मेरे लिए कुछ भी नहीं है, जैसे कि @342342और code relocation info
UpTheCreek

9

एक सामान्य सिद्धांत के रूप में आप जितना संभव हो उतना कैश करना चाहते हैं और अपने लूप के प्रत्येक रन के लिए कम से कम निर्माण और विनाश करना चाहते हैं।

पहली बात यह है कि मेरे सिर में चबूतरे को अपने मुख्य लूप के अंदर अनाम कार्यों (यदि आपके पास कोई है) का उपयोग कम करना है। इसके अलावा उन वस्तुओं को बनाने और नष्ट करने में आसानी होगी जो अन्य कार्यों में पारित हो जाती हैं। मैं किसी भी तरह से एक जावास्क्रिप्ट विशेषज्ञ नहीं हूँ, लेकिन मुझे लगता है कि यह होगा:

var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
    //do something
}

while(true)
{
    $.each(listofthings, loopfunc);

    options.ChangingVariable = newvalue;
    someOtherFunction(options);
}

इससे बहुत तेज़ चलेगा:

while(true)
{
    $.each(listofthings, function(){
        //do something on the list
    });

    someOtherFunction({
        var1: value1,
        var2: value2,
        ChangingVariable: newvalue
    });
}

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

क्षमा करें यदि आपने पहले से ही जो सोचा और सोचा है, उसकी तुलना में यह थोड़ा तुच्छ है।


यह। प्लस भी अन्य कार्यों के अंदर उल्लिखित कार्य (जो IIFE नहीं हैं) भी आम दुरुपयोग है जो बहुत सारी स्मृति को जलाता है और याद रखना आसान है।
एस्लेइजा

धन्यवाद क्रिस! मेरे पास दुर्भाग्य से कोई डाउनटाइम नहीं है: /
UpTheCreek

4

मैं एक या कुछ ऑब्जेक्ट बनाऊंगा global scope(जहाँ मुझे यकीन है कि कचरा इकट्ठा करने वाले को उन्हें छूने की अनुमति नहीं है), तो मैं उन वस्तुओं का उपयोग करने के लिए अपने समाधान को फिर से भरने की कोशिश करूँगा, बजाय स्थानीय चर का उपयोग करने के ।

बेशक यह कोड में हर जगह नहीं किया जा सकता है, लेकिन आम तौर पर यह कचरा कलेक्टर से बचने का मेरा तरीका है।

PS यह कोड के उस विशिष्ट भाग को थोड़ा कम बनाए रखने योग्य बना सकता है।


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