[...] (दी गई, माइक्रोसेकंड पर्यावरण में) [...]
यदि हम लाखों-अरबों चीजों को लूप कर रहे हैं, तो माइक्रो-सेकंड जुड़ जाते हैं। C ++ से एक व्यक्तिगत vtune / माइक्रो-ऑप्टिमाइज़ेशन सत्र (कोई एल्गोरिदम सुधार नहीं):
T-Rex (12.3 million facets):
Initial Time: 32.2372797 seconds
Multithreading: 7.4896073 seconds
4.9201039 seconds
4.6946372 seconds
3.261677 seconds
2.6988536 seconds
SIMD: 1.7831 seconds
4-valence patch optimization: 1.25007 seconds
0.978046 seconds
0.970057 seconds
0.911041 seconds
"मल्टीथ्रेडिंग", "SIMD" (कंपाइलर को हराने के लिए हस्तलिखित), और 4-वैलेंस पैच ऑप्टिमाइज़ेशन के अलावा सब कुछ माइक्रो-लेवल मेमोरी ऑप्टिमाइज़ेशन थे। साथ ही 32 सेकंड के शुरुआती समय से शुरू होने वाला मूल कोड पहले से ही काफी थोड़ा (सैद्धांतिक रूप से इष्टतम एल्गोरिथम जटिलता) अनुकूलित किया गया था और यह हालिया सत्र है। इस हालिया सत्र से बहुत पहले के मूल संस्करण को संसाधित होने में 5 मिनट का समय लगा।
स्मृति दक्षता का अनुकूलन एक एकल-थ्रेडेड संदर्भ में परिमाण के आदेशों के लिए कई बार कहीं से भी मदद कर सकता है, और मल्टीथ्रेडेड संदर्भों में अधिक (मिश्रण में कई थ्रेड्स के साथ एक कुशल मेमोरी प्रतिनिधि के लाभ अक्सर गुणा करते हैं)।
माइक्रो-ऑप्टिमाइज़ेशन के महत्व पर
मैं इस विचार से थोड़ा उत्तेजित हो जाता हूं कि सूक्ष्म अनुकूलन समय की बर्बादी है। मैं मानता हूं कि यह अच्छी सामान्य सलाह है, लेकिन हर कोई माप के बजाय गलत तरीके और अंधविश्वास पर आधारित नहीं है। सही ढंग से किया, यह जरूरी नहीं कि एक सूक्ष्म प्रभाव उत्पन्न करता है। यदि हम Intel के स्वयं के एम्ब्री (कर्नेल को घुमाने) लेते हैं और केवल साधारण स्केलर बीवीएच का परीक्षण करते हैं, तो उन्होंने लिखा है (रे पैकेट नहीं जिसे पीटना बहुत कठिन है), और फिर उस डेटा संरचना के प्रदर्शन को हराकर देखें, यह सबसे अधिक हो सकता है दशकों तक प्रोफाइलिंग और ट्यूनिंग कोड के लिए एक अनुभवी के लिए भी विनम्र अनुभव। और यह सब माइक्रो-अनुकूलन लागू होने के कारण है। उनका समाधान प्रति सेकंड सौ मिलियन से अधिक किरणों को संसाधित कर सकता है जब मैंने औद्योगिक पेशेवरों को काम करते हुए देखा है जो '
बीवीएच का सीधा कार्यान्वयन केवल एल्गोरिदमिक फोकस के साथ करने का कोई तरीका नहीं है और किसी भी अनुकूलन कंपाइलर (यहां तक कि इंटेल का अपना आईसीसी) के खिलाफ प्रति सेकंड सौ मिलियन से अधिक प्राथमिक रे चौराहों को प्राप्त करना है। एक सीधा-साधा व्यक्ति अक्सर प्रति सेकंड एक लाख किरण भी नहीं पाता है। यह पेशेवर-गुणवत्ता के समाधान लेता है यहां तक कि अक्सर प्रति सेकंड कुछ मिलियन किरणें भी प्राप्त होती हैं। प्रति सेकंड सौ मिलियन से अधिक किरणें प्राप्त करने के लिए इंटेल-स्तरीय माइक्रो-ऑप्टिमाइज़ेशन होता है।
एल्गोरिदम
मुझे लगता है कि माइक्रो-ऑप्टिमाइज़ेशन महत्वपूर्ण नहीं है जब तक कि प्रदर्शन मिनटों से सेकंड के स्तर पर महत्वपूर्ण नहीं है, उदाहरण के लिए, या घंटे से मिनट। यदि हम बबल सॉर्ट की तरह एक भयानक एल्गोरिथ्म लेते हैं और इसे उदाहरण के रूप में बड़े पैमाने पर इनपुट पर उपयोग करते हैं, और फिर मर्ज सॉर्ट के एक बुनियादी कार्यान्वयन के लिए इसकी तुलना करते हैं, तो पूर्व में प्रक्रिया में महीनों लग सकते हैं, बाद में 12 मिनट हो सकते हैं। द्विघात बनाम रैखिकतात्मक जटिलता।
महीनों और मिनटों के बीच का अंतर संभवतः अधिकांश लोगों को बनाने जा रहा है, यहां तक कि वे जो प्रदर्शन-महत्वपूर्ण क्षेत्रों में काम नहीं कर रहे हैं, निष्पादन समय को अस्वीकार्य मानते हैं यदि इसके परिणामस्वरूप उपयोगकर्ताओं को परिणाम प्राप्त करने के लिए महीनों की प्रतीक्षा करनी पड़े।
इस बीच, यदि हम गैर-सूक्ष्म-अनुकूलित, सीधे मर्ज सॉर्ट की तुलना क्विकसर्ट से करते हैं (जो कि सॉर्ट करने के लिए एल्गोरिथम से बेहतर नहीं है, और केवल संदर्भ के स्थानीयता के लिए सूक्ष्म-स्तर में सुधार प्रदान करता है), तो माइक्रो-ऑप्टिमाइज़ किए गए क्विकर में समाप्त हो सकता है। 12 मिनट के विपरीत 15 सेकंड। उपयोगकर्ताओं को 12 मिनट तक इंतजार करना पूरी तरह से स्वीकार्य हो सकता है (कॉफी ब्रेक का समय)।
मुझे लगता है कि यह अंतर संभवतः 12 मिनट और 15 सेकंड के बीच के अधिकांश लोगों के लिए नगण्य है, और इसलिए माइक्रो-ऑप्टिमाइज़ेशन को अक्सर बेकार माना जाता है क्योंकि यह अक्सर केवल मिनट और सेकंड के बीच का अंतर होता है, और मिनट और महीने नहीं। दूसरा कारण मुझे लगता है कि इसे बेकार माना जाता है, यह अक्सर उन क्षेत्रों पर लागू होता है जो मायने नहीं रखते हैं: कुछ छोटे क्षेत्र जो कि लूप और क्रिटिकल भी नहीं हैं, जो कुछ संदिग्ध 1% अंतर पैदा करते हैं (जो बहुत अच्छी तरह से सिर्फ शोर हो सकता है)। लेकिन ऐसे लोगों के लिए जो इस प्रकार के समय के मतभेदों की परवाह करते हैं और इसे मापने और सही करने के लिए तैयार हैं, मुझे लगता है कि यह स्मृति पदानुक्रम की कम से कम मूल अवधारणाओं पर ध्यान देने योग्य है (विशेषकर पृष्ठ दोष और कैश मिस से संबंधित ऊपरी स्तर) ।
जावा माइक्रो-ऑप्टिमाइज़ेशन के लिए बहुत सारे कमरे छोड़ता है
क्षमा करें, क्षमा करें - उस तरह के शेख़ी के साथ:
क्या जेवीएम का "जादू" जावा में माइक्रो-ऑप्टिमाइज़ेशन पर एक प्रोग्रामर के प्रभाव को बाधित करता है?
थोड़ा सा लेकिन उतना नहीं जितना लोग सोच सकते हैं अगर आप इसे सही करते हैं। उदाहरण के लिए, यदि आप इमेज प्रोसेसिंग कर रहे हैं, तो मूल कोड में हस्तलिखित SIMD, मल्टीथ्रेडिंग, और मेमोरी ऑप्टिमाइजेशन (एक्सेस पैटर्न और संभवतः इमेज प्रोसेसिंग एल्गोरिदम के आधार पर प्रतिनिधित्व भी), यह 32 के लिए प्रति सेकंड लाखों पिक्सेल प्रति सेकंड क्रंच करना आसान है- बिट RGBA पिक्सेल (8-बिट कलर चैनल) और कभी-कभी प्रति सेकंड अरबों भी।
जावा में कहीं भी पास होना असंभव है यदि आप कहते हैं, तो एक Pixel
वस्तु बनाई गई है (यह अकेले पिक्सेल का आकार 4 बाइट्स से 16 से 64-बिट पर बढ़ेगा)।
लेकिन यदि आप Pixel
ऑब्जेक्ट को टालते हैं, तो बाइट्स की एक सरणी का उपयोग करते हैं, और एक Image
ऑब्जेक्ट को मॉडल करते समय आप बहुत करीब आ सकते हैं । यदि आप सादे पुराने डेटा के सरणियों का उपयोग करना शुरू करते हैं, तो जावा का अभी भी बहुत सक्षम है। मैंने जावा में पहले इस तरह की चीजों की कोशिश की है और काफी प्रभावित हुआ है बशर्ते कि आप हर जगह छोटी नन्ही वस्तुओं का एक गुच्छा न बनाएं जो सामान्य से 4 गुना बड़ा हो (उदा: int
इसके बजाय का उपयोग करें Integer
) और एक तरह से थोक इंटरफेस मॉडलिंग करना शुरू करें Image
इंटरफ़ेस, इंटरफ़ेस नहीं Pixel
। मैं यह कहने के लिए भी उद्यम करूंगा कि यदि आप सादे पुराने डेटा पर लूपिंग कर रहे हैं तो जावा सी ++ परफॉर्मेंस को टक्कर दे सकता है, न कि ऑब्जेक्ट्स ( float
जैसे, नहीं Float
) की विशाल सरणियों को ।
शायद स्मृति आकारों से भी अधिक महत्वपूर्ण यह है कि एक सरणी int
एक सन्निहित प्रतिनिधित्व की गारंटी देती है। की एक सरणी Integer
नहीं है। संदर्भ के स्थानीयता के लिए योगदान अक्सर आवश्यक होता है क्योंकि इसका मतलब है कि कई तत्व (उदा: 16 ints
) सभी एक कैश लाइन में फिट हो सकते हैं और संभावित रूप से कुशल मेमोरी एक्सेस पैटर्न के साथ बेदखल होने से पहले एक साथ पहुंच सकते हैं। इस बीच एक Integer
स्मृति आसपास की स्मृति में अप्रासंगिक होने के साथ कहीं-कहीं फंसी हो सकती है, केवल एक पूर्णांक का उपयोग करने के लिए केवल 16 पूर्णांकों का विरोध करने के लिए मेमोरी के उस क्षेत्र को कैश लाइन में लोड किया गया है। भले ही हम अद्भुत और भाग्यशाली होIntegers
स्मृति में एक दूसरे के ठीक बगल में थे, हम केवल 4 को कैश लाइन में फिट कर सकते हैं जो कि Integer
4 गुना बड़ा होने के परिणामस्वरूप बेदखल होने से पहले पहुँचा जा सकता है , और यह सबसे अच्छी स्थिति में है।
और बहुत सारे माइक्रो-ऑप्टिमाइज़ेशन होने की संभावना है क्योंकि हम एक ही मेमोरी आर्किटेक्चर / पदानुक्रम के तहत एकीकृत होते हैं। मेमोरी एक्सेस पैटर्न कोई फर्क नहीं पड़ता कि आप किस भाषा का उपयोग करते हैं, लूप टाइलिंग / ब्लॉकिंग जैसी अवधारणाएं आमतौर पर C या C ++ में अधिक बार लागू की जा सकती हैं, लेकिन वे जावा को अधिक से अधिक लाभान्वित करती हैं।
मैंने हाल ही में C ++ में पढ़ा है कभी-कभी डेटा सदस्यों का आदेश अनुकूलन प्रदान कर सकता है [...]
डेटा सदस्यों का क्रम आमतौर पर जावा में मायने नहीं रखता है, लेकिन यह ज्यादातर अच्छी बात है। C और C ++ में, ABI कारणों के लिए डेटा सदस्यों के क्रम को संरक्षित करना अक्सर महत्वपूर्ण होता है, ताकि कंपाइलर उसके साथ गड़बड़ न करें। वहां काम करने वाले मानव डेवलपर्स को ऐसी चीजों को करने में सावधानी बरतनी चाहिए जो पैडिंग पर मेमोरी बर्बाद करने से बचने के लिए अपने डेटा सदस्यों को अवरोही क्रम (सबसे बड़ी से छोटी) में व्यवस्थित करें। जावा के साथ, जाहिरा तौर पर जेआईटी आपके लिए सदस्यों को उड़ाने के लिए पुन: व्यवस्थित कर सकता है ताकि पैडिंग को कम करते समय उचित संरेखण सुनिश्चित किया जा सके, बशर्ते कि यह कुछ ऐसा हो जो औसत C और C ++ प्रोग्रामर अक्सर खराब कर सकते हैं और इस तरह से बर्बाद कर सकते हैं ( जो सिर्फ स्मृति को बर्बाद नहीं कर रहा है, लेकिन अक्सर एओएस संरचनाओं के बीच स्ट्राइड को बढ़ाकर और अधिक कैश मिस होने के कारण गति को बर्बाद कर रहा है)। यह पैडिंग को कम करने के लिए खेतों को पुनर्व्यवस्थित करने के लिए एक बहुत ही रोबोटिक चीज़ है, इसलिए आदर्श रूप से मनुष्य उससे निपटते नहीं हैं। एकमात्र समय जहां फ़ील्ड व्यवस्था इस तरह से महत्वपूर्ण हो सकती है कि किसी व्यक्ति को इष्टतम व्यवस्था को जानने के लिए आवश्यकता होती है यदि ऑब्जेक्ट 64 बाइट्स से बड़ा है और हम एक्सेस पैटर्न (इष्टतम पैडिंग नहीं) के आधार पर फ़ील्ड्स की व्यवस्था कर रहे हैं - किस स्थिति में एक अधिक मानवीय प्रयास हो सकता है (महत्वपूर्ण रास्तों को समझने की आवश्यकता है, जिनमें से कुछ जानकारी है कि एक संकलक संभवतः यह जानकर बिना अनुमान नहीं लगा सकता है कि उपयोगकर्ता सॉफ़्टवेयर के साथ क्या करेंगे)।
यदि नहीं, तो लोग उदाहरण दे सकते हैं कि आप जावा (सरल संकलक झंडे के अलावा) में किन चालों का उपयोग कर सकते हैं।
जावा और C ++ के बीच एक अनुकूलन मानसिकता के संदर्भ में मेरे लिए सबसे बड़ा अंतर यह है कि C ++ आपको प्रदर्शन-महत्वपूर्ण परिदृश्य में जावा की तुलना में वस्तुओं को थोड़ा (नन्हा) बिट का उपयोग करने की अनुमति दे सकता है। उदाहरण के लिए, C ++ एक पूर्णांक को एक वर्ग के साथ लपेट सकता है जिसमें कोई ओवरहेड नहीं है (सभी जगह बेंचमार्क किए गए)। जावा के पास मेटाडेटा पॉइंटर-स्टाइल + अलाइनमेंट पैडिंग ओवरहेड प्रति ऑब्जेक्ट है, यही कारण है कि Boolean
इससे बड़ा है boolean
(लेकिन बदले में प्रतिबिंब के समान लाभ प्रदान करता है और किसी भी फ़ंक्शन को ओवरराइड करने की क्षमता final
हर एक यूडीटी के लिए चिह्नित नहीं है )।
गैर-सजातीय क्षेत्रों में स्मृति लेआउट की समीपता को नियंत्रित करने के लिए C ++ में यह थोड़ा आसान है (उदाहरण के लिए: एक संरचना / वर्ग के माध्यम से एक सरणी में फ्लोट और इन्टस को समतल करना), क्योंकि स्थानिक स्थानीयता अक्सर खो जाती है (या कम से कम नियंत्रण खो जाता है) जावा में जब जीसी के माध्यम से वस्तुओं का आवंटन किया जाता है।
... लेकिन अक्सर उच्चतम-प्रदर्शन समाधान अक्सर उन लोगों को विभाजित करेंगे और सादे पुराने डेटा के सन्निहित सरणियों पर एक SoA पहुंच पैटर्न का उपयोग करेंगे। इसलिए जिन क्षेत्रों में चरम प्रदर्शन की आवश्यकता है, जावा और सी ++ के बीच मेमोरी लेआउट को अनुकूलित करने की रणनीति अक्सर एक ही होती है, और अक्सर आपने संग्रह-शैली के इंटरफेस के पक्ष में उन नन्हे ऑब्जेक्ट-उन्मुख इंटरफेस को ध्वस्त किया होगा जो गर्म / जैसी चीजें कर सकते हैं ठंडे क्षेत्र में बंटवारा, SoA प्रतिनिधि, आदि गैर-सजातीय AoSoA प्रतिनिधि जावा में असंभव की तरह प्रतीत होते हैं (जब तक कि आपने बाइट के कच्चे सरणी या ऐसा कुछ नहीं किया था), लेकिन वे दुर्लभ मामलों के लिए हैं जहां दोनोंअनुक्रमिक और यादृच्छिक अभिगम पैटर्न एक साथ गर्म क्षेत्रों के लिए फ़ील्ड प्रकारों का मिश्रण होने के दौरान तेज़ होने की आवश्यकता है। यदि आप चोटी के प्रदर्शन के लिए पहुंच रहे हैं, तो इन दोनों के बीच अनुकूलन रणनीति के अंतर के सामान्य स्तर पर (सामान्य स्तर पर) अंतर का थोक मूल्य है।
यदि आप केवल "अच्छे" प्रदर्शन के लिए पहुंच रहे हैं, तो मतभेद बहुत अधिक भिन्न होते हैं - Integer
बनाम छोटी वस्तुओं के साथ ज्यादा कुछ करने में सक्षम नहीं int
होना चाहिए , जैसे कि पीआईटीए का थोड़ा अधिक हो सकता है, खासकर जिस तरह से यह जेनेरिक के साथ बातचीत करता है। । यह जावा में एक केंद्रीय अनुकूलन लक्ष्य के रूप में बस का निर्माण एक सामान्य डेटा संरचना करने के लिए थोड़ा मुश्किल है कि के लिए काम करता है int
, float
आदि, जबकि उन बड़ा और महंगा UDTs से परहेज है, लेकिन अक्सर सबसे प्रदर्शन महत्वपूर्ण क्षेत्रों हाथ से रोलिंग अपनी खुद की डाटा संरचनाओं की आवश्यकता होगी वैसे भी एक बहुत ही विशिष्ट उद्देश्य के लिए तैयार है, इसलिए यह केवल उस कोड के लिए कष्टप्रद है जो अच्छे प्रदर्शन के लिए प्रयास कर रहा है लेकिन चोटी के प्रदर्शन के लिए नहीं।
ऑब्जेक्ट ओवरहेड
ध्यान दें कि जावा ऑब्जेक्ट ओवरहेड (मेटाडेटा और स्थानिक स्थानीयता का नुकसान और प्रारंभिक जीसी चक्र के बाद अस्थायी स्थानीयता का नुकसान) अक्सर उन चीजों के लिए बड़ा होता है जो वास्तव में छोटे होते हैं (जैसे int
बनाम Integer
) जो कुछ डेटा संरचना में लाखों लोगों द्वारा संग्रहीत किया जा रहा है। बड़े पैमाने पर सन्निहित और बहुत तंग छोरों में पहुँचा। इस विषय के बारे में बहुत अधिक संवेदनशीलता प्रतीत होती है, इसलिए मुझे स्पष्ट करना चाहिए कि आप छवियों जैसे बड़ी वस्तुओं के लिए ऑब्जेक्ट ओवरहेड के बारे में चिंता नहीं करना चाहते हैं, बस एक पिक्सेल की तरह वास्तव में छोटी वस्तुएं।
यदि किसी को इस भाग के बारे में संदेह है, तो मैं एक लाख यादृच्छिक ints
बनाम एक लाख यादृच्छिक के बीच एक बेंचमार्क बनाने का सुझाव दूंगा Integers
और इसे बार-बार करने के लिए ( Integers
प्रारंभिक जीसी चक्र के बाद स्मृति में फेरबदल होगा)।
अल्टिमेट ट्रिक: इंटरफ़ेस डिज़ाइन जो ऑप्टिमाइज़ करने के लिए कमरे को छोड़ दें
तो परम जावा ट्रिक जैसा कि मैं इसे देखता हूं अगर आप एक ऐसी जगह से निपट रहे हैं जो छोटी वस्तुओं पर भारी भार संभालती है (उदाहरण: ए Pixel
, 4-वेक्टर, 4x4 मैट्रिक्स, ए Particle
, संभवत: यहां तक कि Account
अगर यह केवल कुछ छोटी है फ़ील्ड) इन नन्हा चीजों के लिए वस्तुओं का उपयोग करने से बचने और सादे पुराने डेटा के सरणियों (संभवतः एक साथ जंजीर) का उपयोग करना है। तो संग्रह इंटरफेस बन वस्तुओं की तरह Image
, ParticleSystem
, Accounts
,, व्यक्तिगत लोगों सूचकांक द्वारा पहुँचा जा सकता मैट्रिक्स या वैक्टर, आदि का एक संग्रह है, जैसे यह भी सी और सी में अंतिम डिजाइन चाल ++ में से एक है के बाद से भी है कि बुनियादी वस्तु भूमि के ऊपर के बिना और स्मृति से विमुख, एकल कण के स्तर पर इंटरफ़ेस को मॉडलिंग करना सबसे कुशल समाधानों को रोकता है।