उत्पादक अनुप्रयोग
एंड्रॉइड प्लेटफॉर्म के लिए आवेदन एक मोबाइल डिवाइस पर सीमित कंप्यूटिंग क्षमताओं और मेमोरी के साथ, और एक छोटी बैटरी जीवन के साथ लॉन्च किया जाएगा। तो, आवेदन प्रभावी होना चाहिए। बैटरी जीवन एक कारण है कि आप अपने एप्लिकेशन को ऑप्टिमाइज़ करना चाहते हैं, भले ही यह काफी तेजी से चलता हो। उपयोगकर्ताओं के लिए बैटरी जीवन बहुत महत्वपूर्ण है, और एंड्रॉइड प्लेटफ़ॉर्म उपयोगकर्ता को आसानी से दिखाएगा यदि एप्लिकेशन इसे काफी कम करता है।
यद्यपि यहां सूक्ष्म-अनुकूलन का वर्णन किया जाएगा, वे लगभग कभी भी आपके एप्लिकेशन को नुकसान नहीं पहुंचा पाएंगे। सही एल्गोरिदम और डेटा संरचनाओं को चुनना हमेशा पहली प्राथमिकताएं होनी चाहिए, लेकिन इस पहलू पर विचार नहीं किया जाएगा।
परिचय
उत्पादक कोड विकसित करने के लिए सिर्फ दो बुनियादी नियम:
- वह काम न करें जो आपको करने की आवश्यकता नहीं है
- स्मृति को आवंटित न करें जो आप आवंटित नहीं कर सकते हैं
बुद्धिमानी से अनुकूलन करें
हम एंड्रॉइड के लिए माइक्रो-ऑप्टिमाइजेशन का वर्णन करेंगे, इसलिए यह माना जाता है कि आपने पहले से ही यह निर्धारित करने के लिए कि आपको किस कोड के किस विशेष कोड को ऑप्टिमाइज़ करना है, यह निर्धारित करने के लिए प्रोफाइलर का उपयोग किया है, और आप पहले से ही जानते हैं कि आपके द्वारा किए गए परिवर्तनों के प्रभाव का मूल्यांकन कैसे किया जाए। आपने विकास में बहुत समय लगाया है, इसलिए यह जानना महत्वपूर्ण है कि आप इसे बुद्धिमानी से खर्च कर रहे हैं।
यह भी माना जाता है कि आपने पहले से ही सर्वश्रेष्ठ एल्गोरिदम और डेटा संरचनाओं को चुना है, और प्रदर्शन पर अपने एपीआई निर्णयों के प्रभाव की भविष्यवाणी की है। सही डेटा संरचनाओं और एल्गोरिदम के उपयोग से इनमें से किसी भी सुझाव से प्रदर्शन में सुधार होता है, और प्रदर्शन पर एपीआई के प्रभाव को ध्यान से देखते हुए भविष्य में एक बेहतर कार्यान्वयन के लिए संक्रमण की सुविधा होगी (जो कि एप्लिकेशन कोड की तुलना में लाइब्रेरी कोड के लिए मुख्य रूप से महत्वपूर्ण है)।
एंड्रॉइड एप्लिकेशन के माइक्रोप्टीमाइजेशन के दौरान आपके द्वारा सामना की जाने वाली सबसे मुश्किल कठिनाइयों में से एक यह है कि आपके एप्लिकेशन को कई हार्डवेयर प्लेटफॉर्म पर चलने की सबसे अधिक संभावना है। विभिन्न प्रोसेसरों पर वर्चुअल मशीन के विभिन्न संस्करण अलग-अलग गति से काम करते हैं। सामान्य तौर पर, यह तब भी नहीं होता है जब आप बस यह कह सकते हैं कि "डिवाइस X डिवाइस Y की तुलना में अधिक तेज / धीमा है" और परिणाम को अन्य उपकरणों के लिए एक्सट्रपलेट करें। विशेष रूप से, एक एमुलेटर पर परीक्षण किसी भी डिवाइस पर प्रदर्शन के बारे में लगभग कुछ भी नहीं कहता है। JIT के साथ और इसके बिना उपकरणों के बीच बहुत बड़ा अंतर है: JIT वाले डिवाइस के लिए "सर्वश्रेष्ठ" कोड हमेशा उस उपकरण की कमी के लिए सबसे अच्छा नहीं रहता है।
यदि आप जानना चाहते हैं कि एप्लिकेशन किसी डिवाइस पर कैसे व्यवहार करता है, तो इसका परीक्षण करना होगा।
अनावश्यक वस्तुएं बनाने से बचें
वस्तुओं को बनाना कभी भी स्वतंत्र नहीं है। प्रत्येक थ्रेड की अस्थायी वस्तुओं के लिए पीढ़ियों और पूल के साथ काम करने वाला एक कचरा कलेक्टर स्मृति को आवंटित करना आसान बना सकता है, लेकिन स्मृति को आवंटित करना हमेशा आवंटित नहीं करने की तुलना में अधिक महंगा होता है।
यदि आप उपयोगकर्ता इंटरफ़ेस में एक लूप में ऑब्जेक्ट्स का चयन करते हैं, तो आप एक आवधिक कचरा संग्रह को बल देते हैं, जिससे उपयोगकर्ता को दिखाई देने वाले छोटे "स्टेटर" बनते हैं। जिंजरब्रेड में पेश किए गए समानांतर कचरा कलेक्टर इसकी मदद कर सकते हैं, लेकिन अनावश्यक काम से हमेशा बचा जाना चाहिए।
इसलिए, आपको उन वस्तुओं को बनाने से बचना चाहिए जिनकी आवश्यकता नहीं है। यहाँ कुछ उदाहरण हैं जो मदद कर सकते हैं:
- यदि कोई ऐसा तरीका है जो एक स्ट्रिंग लौटाता है और यह ज्ञात है कि परिणाम हमेशा स्ट्रिंगबर्फर में जोड़ा जाएगा, तो कार्यान्वयन को बदल दें ताकि विधि एक अल्पकालिक अस्थायी ऑब्जेक्ट बनाने के बजाय सीधे इसके अतिरिक्त प्रदर्शन करे।
- इनपुट डेटा सेट से एक स्ट्रिंग निकालते समय, प्रतिलिपि बनाने के बजाय प्रारंभिक डेटा के एक विकल्प को वापस करने का प्रयास करें। एक नई स्ट्रिंग ऑब्जेक्ट बनाई जाएगी, लेकिन इसके लिए वर्णों की सरणी और प्रारंभिक डेटा साझा किए जाएंगे। (समझौता यह है कि यदि प्रारंभिक इनपुट का केवल एक छोटा हिस्सा उपयोग किया जाता है, तो यह अभी भी इसकी संपूर्णता में संग्रहीत किया जाएगा, यदि आप इस सलाह का पालन करते हैं)।
चलो कुछ और अधिक कट्टरपंथी लेते हैं: बहुआयामी सरणियों को एक समानांतर एक आयामी सरणी में विभाजित करते हैं:
- एक int array एक Integer array से काफी बेहतर है। लेकिन इस तथ्य को सामान्यीकृत किया जा सकता है: दो समानांतर int सरणियां वस्तुओं की एक सरणी (int, int) की तुलना में बहुत अधिक कुशल हैं। प्राइमेटिक्स के किसी भी संयोजन के लिए वही जाता है।
- यदि आपको एक कंटेनर को लागू करने की आवश्यकता है जिसमें जोड़े (फू, बार) शामिल हैं, तो याद रखें कि दो समानांतर सरणियों फू [] और बार [] आम तौर पर वस्तुओं के एकल सरणी (फू, बार) की तुलना में बहुत बेहतर हैं। (अपवाद तब होता है जब आप एक एपीआई विकसित कर रहे होते हैं; इन मामलों के लिए, प्रदर्शन के लिए एक छोटी सी पहचान के लिए एक अच्छी एपीआई से चिपकना बेहतर होता है। लेकिन आपको अपने स्वयं के आंतरिक कोड में यथासंभव कुशल होने की कोशिश करनी चाहिए।)
सामान्यतया, यदि संभव हो तो अल्पकालिक वस्तुओं को बनाने से बचें। निर्मित वस्तुओं की एक छोटी संख्या का मतलब है कि कचरा संग्रह कम लगातार होता है, जो सीधे उपयोगकर्ता के साथ बातचीत को प्रभावित करता है।
प्रदर्शन मिथक
इस मैनुअल के पुराने संस्करणों में कई गलत कथन हैं। हम उनमें से कुछ को संबोधित करेंगे।
जेआईटी के बिना उपकरणों पर, किसी विशेष वर्ग के ऑब्जेक्ट पर कॉल करने के तरीके एक इंटरफ़ेस के माध्यम से कॉल करने की तुलना में थोड़ा तेज है। (इस प्रकार, मैप के बजाय हैशपॉप विधियों को कॉल करना सस्ता है, भले ही यह एक ही वस्तु हो।) 2 बार तेजी से नहीं। वास्तविक संख्या 6% के करीब है। इसके अलावा, JIT के साथ अंतर बिल्कुल भी अस्वीकार्य है।
जेआईटी के बिना उपकरणों पर, क्लास फ़ील्ड्स तक कैशिंग पहुँच सीधे क्षेत्र में कॉल दोहराने से 20% तेज है। जेआईटी के साथ, एक क्षेत्र तक पहुंचने की लागत एक स्थानीय पते तक पहुंचने की लागत के बराबर है, इसलिए इस अनुकूलन की आवश्यकता नहीं है जब तक कि ऐसा नहीं लगता कि यह कोड को अधिक पठनीय बनाता है। (जो अंतिम, स्थिर और स्थिर अंतिम क्षेत्रों के बारे में सच है)।
आभासी के लिए स्थिर पसंद करते हैं
यदि ऑब्जेक्ट के क्षेत्रों तक पहुंचने की कोई आवश्यकता नहीं है, तो विधि को स्थिर बनाया जा सकता है। कॉल 15-20% तेज होगी। यह अच्छा है क्योंकि यह हस्ताक्षर द्वारा कहा जा सकता है कि विधि वस्तु की स्थिति को नहीं बदलती है।
आंतरिक अभिगम विधियों से बचें
मूल भाषाओं में, जैसे कि C ++, डायरेक्ट एक्सेस (i = mCount) के बजाय गेटर्स (जैसे i = getCount ()) का उपयोग करना अच्छा है। यह C ++ के लिए एक अद्भुत आदत है, क्योंकि कंपाइलर आमतौर पर इनलाइन प्रतिस्थापन का प्रदर्शन कर सकता है, और यदि आपको फ़ील्ड एक्सेस को सीमित करने या डिबग करने की आवश्यकता है, तो आप किसी भी समय आवश्यक कोड जोड़ सकते हैं।
Android के लिए, यह एक बुरा विचार है। आभासी तरीकों को कॉल करना काफी महंगा है - वस्तु क्षेत्रों की खोज की तुलना में बहुत अधिक महंगा है। बेशक, सामान्य ओओपी प्रथाओं का उपयोग और इंटरफ़ेस में गेटर्स और सेटर का उपयोग उचित है, लेकिन कक्षा के अंदर आपको हमेशा सीधे खेतों तक पहुंचना चाहिए।
जेआईटी के बिना, क्षेत्र में सीधी पहुंच एक नियमित गेटर को कॉल करने की तुलना में लगभग 3 गुना तेज है। जेआईटी के साथ, जहां एक स्थानीय पते तक सीधी पहुंच के लिए सीधी पहुंच समान है, एक एक्सेस पद्धति को कॉल करने की तुलना में प्रत्यक्ष पहुंच लगभग 7 गुना तेज होगी। यह कथन फ्रायो के लिए सही है, लेकिन भविष्य में रिलीज में इस तथ्य के कारण स्थिति में सुधार होगा कि जेआईटी इनलाइन प्राप्त करता है।
स्थिरांक के लिए स्थैतिक फाइनल का उपयोग करें
कक्षा की शुरुआत में निम्नलिखित घोषणा पर विचार करें:
static int intVal = 42; static String strVal = "Hello, world!";
कंपाइलर एक क्लास इनिशियलाइज़ेशन मेथड जेनरेट करता है, एक ऐसे नाम के साथ जिसे पहली बार क्लास में इस्तेमाल करने पर निष्पादित किया जाता है। विधि 42 को intVal असाइन करती है और strVal के लिए वर्ग फ़ाइल की अपरिवर्तनीय लाइनों की तालिका से लिंक को पुनः प्राप्त करती है। जब ये चर एक्सेस हो जाते हैं, तो कक्षा के संबंधित क्षेत्रों को खोजा जाएगा।
यहां बताया गया है कि हम इस व्यवहार को एक अंतिम कीवर्ड के साथ कैसे बदल सकते हैं:
static final int intVal = 42; static final String strVal = "Hello, world!";
विधि की अब आवश्यकता नहीं है, क्योंकि स्थिरांक डेक्स फ़ाइल में स्थिर फ़ील्ड इनिशियलाइज़र को लिखे जाते हैं। कोड जो intVal तक पहुँचता है, पूर्णांक मान 42 का सीधे उपयोग करता है, और strVal तक पहुँचने के लिए फ़ील्ड की तलाश करने के बजाय एक स्ट्रिंग के लिए एक सस्ती कॉल का कारण होगा। (ध्यान दें कि यह अनुकूलन केवल प्राथमिकताओं और तारों के लिए काम करता है, सभी मनमाने ढंग से लिंक प्रकारों के बावजूद नहीं। इसके बावजूद, स्थिर अंतिम के रूप में निरंतर घोषणाओं का उपयोग जहां भी संभव हो, करना चाहिए।)
लूप सिंटैक्स के लिए सुधार का उपयोग करना
प्रत्येक लूप का उपयोग संग्रह के लिए किया जा सकता है जो
Iterable
इंटरफ़ेस और सरणियों को लागू करता है। संग्रह के लिए hasNext () और अगले () तरीकों को लागू करने के लिए एक पुनरावृत्तिक आवंटित किया जाता है।
ArrayList
काउंटर के साथ
ArrayList
क्लासिक लूप लगभग 3 गुना तेजी से (JIT के साथ या बिना) है, लेकिन अन्य संग्रह के लिए, "प्रत्येक के लिए" वाक्यविन्यास स्पष्ट रूप से एक पुनरावृत्ति का उपयोग करने के बराबर होगा।
किसी सरणी को ट्रेस करने के लिए कई विकल्प हैं:
static class Foo { int mSplat; } Foo[] mArray = ... public void zero() { int sum = 0; for (int i = 0; i < mArray.length; ++i) { sum += mArray[i].mSplat; } } public void one() { int sum = 0; Foo[] localArray = mArray; int len = localArray.length; for (int i = 0; i < len; ++i) { sum += localArray[i].mSplat; } } public void two() { int sum = 0; for (Foo a : mArray) { sum += a.mSplat; } }
शून्य () सबसे धीमा तरीका है, क्योंकि JIT पुनरावृति के प्रत्येक चरण में सरणी की लंबाई प्राप्त करने का अनुकूलन नहीं कर सकता है।
एक () तेज है। यह क्षेत्रिय खोजों से बचते हुए आवश्यक सूचनाओं को स्थानीय चरों में खींचता है। केवल array.length यहां प्रदर्शन में सुधार करती है।
दो () JIT के बिना उपकरणों के लिए तेज है और JIT वाले उपकरणों के लिए एक () से अप्रभेद्य है। यह जावा 1.5 में प्रस्तुत सिंटैक्स के लिए विस्तारित का उपयोग करता है
नीचे पंक्ति: डिफ़ॉल्ट रूप से प्रत्येक-वाक्यविन्यास का उपयोग करें, लेकिन प्रदर्शन-महत्वपूर्ण
ArrayList
पास पर मैन्युअल रूप से पुनरावृत्ति करने के बारे में सोचें। (
प्रभावी जावा , अनुच्छेद 46 भी देखें।)
निजी आंतरिक कक्षाओं के लिए निजी के बजाय पैकेज-निजी पहुंच का उपयोग करें।
निम्नलिखित वर्ग परिभाषा पर विचार करें:
public class Foo { private class Inner { void stuff() { Foo.this.doStuff(Foo.this.mValue); } } private int mValue; public void run() { Inner in = new Inner(); mValue = 27; in.stuff(); } private void doStuff(int value) { System.out.println(" " + value); } }
नोट करने के लिए सबसे महत्वपूर्ण बात एक निजी आंतरिक वर्ग (फू $ इनर) की परिभाषा है, जो सीधे निजी पद्धति और बाहरी वर्ग में निजी क्षेत्र को संदर्भित करता है। कोड सही है और उम्मीद के मुताबिक "मूल्य 27" प्रदर्शित करता है।
यहाँ समस्या यह है कि वर्चुअल मशीन Foo के निजी सदस्यों के लिए आंतरिक वर्ग से सीधे पहुंच को अस्वीकार्य मानता है, क्योंकि फू और फू $ इनर अलग-अलग वर्ग हैं, भले ही जावा बाहरी कक्षाओं के निजी सदस्यों तक पहुँचने की अनुमति देता है। इस अंतर को पाटने के लिए, कंपाइलर कुछ कृत्रिम तरीके बनाता है:
static int Foo.access$100(Foo foo) { return foo.mValue; } static void Foo.access$200(Foo foo, int value) { foo.doStuff(value); }
आंतरिक वर्ग इन स्थैतिक तरीकों को हर बार कॉल करता है, जिसे बाहरी वर्ग के mValue या कॉल डस्टफ़ () तक पहुंच की आवश्यकता होती है। इसका मतलब यह है कि ऊपर कोड उस स्थिति में बदल जाता है जब कक्षा के क्षेत्रों तक पहुंच एक्सेसर विधियों के माध्यम से होती है। हमने खेतों तक सीधी पहुंच से पहले इस तरह के तरीकों की सुस्ती के सवाल पर पहले ही चर्चा कर ली है, इसलिए यह पता चला है कि भाषा का एक विशेष मुहावरा "अदृश्य" प्रदर्शन में गिरावट का अनुवाद करता है।
यदि एप्लिकेशन का प्रदर्शन-महत्वपूर्ण टुकड़ा समान कोड का उपयोग करता है, तो निजी के बजाय आंतरिक वर्ग, पैकेज-निजी से एक्सेस किए गए फ़ील्ड और विधियों की घोषणा करके इस व्यवहार से बचा जा सकता है। दुर्भाग्य से, इसका मतलब है कि फ़ील्ड पैकेज में अन्य वर्गों से सुलभ होंगी, इसलिए इस तकनीक का उपयोग सार्वजनिक एपीआई में नहीं किया जा सकता है।
फ्लोटिंग पॉइंट नंबरों का समझदारी से इस्तेमाल करें
संक्षेप में, फ्लोटिंग पॉइंट गणना एंड्रॉइड डिवाइसों पर पूर्णांक की तुलना में लगभग 2 गुना धीमी है। यह G1 (JIT और FPU के बिना) और NexusU के साथ FPU और JIT के लिए सही है। (हालांकि, अंकगणितीय ऑपरेशन की गति के मामले में दोनों उपकरणों के बीच का अंतर लगभग 10 गुना है)।
गति के संदर्भ में, अधिक आधुनिक हार्डवेयर पर फ्लोट और डबल के बीच कोई अंतर नहीं है। मेमोरी से, डबल 2 गुना बड़ा है। डेस्कटॉप कंप्यूटर के लिए, मेमोरी को ध्यान में रखे बिना, आपको फ्लोट के बजाय डबल पसंद करना चाहिए।
इसके अलावा, कुछ चिप्स बोर्ड पर पूर्णांकों के एकीकृत गुणन को करते हैं, लेकिन एकीकृत पूर्णांक विभाजन नहीं है। ऐसे मामलों में, पूर्णांक विभाजन और मोडुलो संचालन सॉफ्टवेयर स्तर पर किए जाते हैं। इस बारे में सोचें अगर आप हैश टेबल लिख रहे हैं या कई गणित ऑपरेशन कर रहे हैं।
पुस्तकालयों को जानें और उनका उपयोग करें
लाइब्रेरी कोड का उपयोग करने के बजाय अपने स्वयं के लिखने के सभी सामान्य कारणों के अलावा, इस तथ्य को ध्यान में रखें कि सिस्टम लाइब्रेरी कोड को कोडांतरक आवेषण के साथ बदल सकता है, जो कि जावा समकक्ष के लिए जेआईटी कंपाइलर द्वारा निर्मित सबसे अच्छे कोड से तेज हो सकता है। एक विशिष्ट उदाहरण
String.indexOf
और अन्य विधियाँ हैं, जो Dalvik आंतरिक कोड के साथ बदल देता है। इस वजह से,
System.arraycopy
लगभग 9 (!) टाइम्स है जो नेक्सस वन पर मैन्युअल रूप से कार्यान्वित लूप की तुलना में एक मौजूदा JIT के साथ तेज है। (
प्रभावी जावा , अनुच्छेद 47 भी देखें।)
देशी तरीकों का समझदारी से इस्तेमाल करें
मूल कोड जावा से अधिक आवश्यक नहीं है। एक कारण के लिए: जावा -> मूल कोड संक्रमण के लिए भुगतान करने की एक कीमत है, और JIT इन सीमाओं के भीतर कुछ भी नहीं कर सकता है। यदि आप देशी संसाधनों का आवंटन करते हैं
(हीप पर मेमोरी, फाइल डिस्क्रिप्टर, या कुछ और), इन संसाधनों को समय पर एकत्र करने की जटिलता स्पष्ट रूप से बढ़ जाती है। आपको प्रत्येक आर्किटेक्चर के लिए कोड भी संकलित करना होगा, जिस पर आप इसे चलाने की योजना बनाते हैं (इसके बजाय इसमें JIT पर भरोसा करने की)। आप समान आर्किटेक्चर के लिए कई संस्करण भी संकलित कर सकते हैं: जी 1 में एआरएम प्रोसेसर के लिए संकलित मूल कोड एक ही प्रोसेसर का पूरा लाभ नहीं ले सकता है, लेकिन नेक्सस वन में, और नेक्सस वन के लिए संकलित कोड बस जी 1 पर नहीं चलता है।
मूल कोड ज्यादातर उपयोगी है अगर कुछ मूल आधार है जिसे आप एंड्रॉइड पर पोर्ट करना चाहते हैं, और जावा एप्लिकेशन के अलग-अलग हिस्सों को गति देने के लिए नहीं। (प्रभावी जावा, पैरा 54 भी देखें।)
अंत में
एक आखिरी बात: हमेशा माप करें। इससे पहले कि आप अनुकूलन करना शुरू करें, सुनिश्चित करें कि आपको कोई समस्या है। सुनिश्चित करें कि आप मौजूदा प्रदर्शन को सही ढंग से माप सकते हैं, अन्यथा आप वैकल्पिक समाधानों से प्राप्त लाभों को माप नहीं पाएंगे।
यहाँ दिए गए प्रत्येक कथन का परीक्षण किया जाता है। स्रोत कोड dalvik परियोजना में code.google.com पर देखे जा सकते हैं।
ये परीक्षण कैलीपर माइक्रोमेसुरिंग फ्रेमवर्क का उपयोग करके लिखे गए हैं। माइक्रो-माप सही ढंग से प्रदर्शन करना मुश्किल है, इसलिए कैलीपर आपको आपके लिए कड़ी मेहनत करने में मदद करता है, और यहां तक कि कुछ मामलों की पहचान भी करता है जहां आप माप नहीं रहे हैं कि आप क्या मापने की कोशिश कर रहे हैं (उदाहरण के लिए, क्योंकि वर्चुअल मशीन ने आपके कोड को पूरी तरह से अनुकूलित किया है)। हम कैलीपर का उपयोग करने के लिए दृढ़ता से सलाह देते हैं कि आप अपने स्वयं के माइक्रोप्रोमेशन का उत्पादन करें।
आप प्रोफ़ाइल के लिए Traceview का भी उपयोग कर सकते हैं, लेकिन यह समझना महत्वपूर्ण है कि यह अब JIT को बंद कर रहा है, जिसके कारण JIT खेल सकता है। ट्रेसव्यू द्वारा सुझाए गए परिवर्तनों को करने के बाद, यह सुनिश्चित करना विशेष रूप से महत्वपूर्ण है कि परिणामी कोड वास्तव में तेजी से निष्पादित होता है यदि यह ट्रेसव्यू के बिना चलाया जाता है।