ByteBuffer.allocate () बनाम ByteBuffer.allocateDirect ()


144

करने के लिए allocate()या allocateDirect(), यह सवाल है।

अब कुछ वर्षों के लिए मैं सिर्फ यह सोचकर फंस गया हूं कि चूंकि DirectByteBufferOS स्तर पर एक सीधी मेमोरी मैपिंग है, इसलिए यह HeapByteBuffers की तुलना में कॉल प्राप्त / डालने के साथ तेज प्रदर्शन करेगा । मुझे अब तक की स्थिति के बारे में सटीक विवरण का पता लगाने में वास्तव में कोई दिलचस्पी नहीं थी। मैं जानना चाहता हूं कि दोनों में से कौन सी ByteBufferतेजी से और किन शर्तों पर होती है।


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

वे SocketChannelगैर-अवरोधक के लिए कॉन्फ़िगर किए गए एस से पढ़ने और लिखने के लिए उपयोग किए जाएंगे । तो @bmargulies ने जो कहा उसके बारे में, DirectByteBufferचैनलों के लिए तेजी से प्रदर्शन करेंगे।

@Gnarly मेरे उत्तर का कम से कम वर्तमान संस्करण कहता है कि चैनलों को लाभ होने की उम्मीद है।
bmargulies

जवाबों:


150

रॉन Hitches अपनी उत्कृष्ट पुस्तक में जावा NIO की पेशकश लगता है कि मुझे क्या लगा कि आपके प्रश्न का एक अच्छा जवाब हो सकता है:

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

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

प्रत्यक्ष बाइट बफ़र आमतौर पर I / O संचालन के लिए सबसे अच्छा विकल्प हैं। डिज़ाइन के अनुसार, वे JVM के लिए उपलब्ध सबसे कुशल I / O तंत्र का समर्थन करते हैं। नॉनडायरेक्ट बाइट बफ़र्स चैनलों को पारित किया जा सकता है, लेकिन ऐसा करने पर प्रदर्शन जुर्माना हो सकता है। आमतौर पर एक nondirect बफ़र के लिए मूल I / O ऑपरेशन का लक्ष्य होना संभव नहीं है। यदि आप लिखने के लिए किसी चैनल के लिए एक नॉनडायरेक्ट बाइटफायर ऑब्जेक्ट पास करते हैं, तो चैनल प्रत्येक पर निम्नलिखित कार्य कर सकता है:

  1. एक अस्थायी प्रत्यक्ष बाइटबफ़र ऑब्जेक्ट बनाएं।
  2. अस्थायी बफ़र के लिए nondirect बफ़र की सामग्री की प्रतिलिपि बनाएँ।
  3. अस्थायी बफर का उपयोग करके निम्न-स्तर I / O ऑपरेशन करें।
  4. अस्थायी बफर ऑब्जेक्ट दायरे से बाहर हो जाता है और अंततः कचरा एकत्र किया जाता है।

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

प्रत्यक्ष बफ़र्स I / O के लिए इष्टतम हैं, लेकिन वे नॉनडायरेक्ट बाइट बफ़र की तुलना में अधिक महंगा हो सकते हैं। डायरेक्ट बफ़र्स द्वारा उपयोग की जाने वाली मेमोरी को देशी, ऑपरेटिंग सिस्टम-विशिष्ट कोड के माध्यम से कॉल करके आवंटित किया जाता है, मानक JVM हीप को दरकिनार करता है। मेजबान ऑपरेटिंग सिस्टम और JVI कार्यान्वयन के आधार पर, प्रत्यक्ष बफ़र्स की तुलना में सीधे बफ़र्स को स्थापित करना और फाड़ना काफी महंगा हो सकता है। प्रत्यक्ष बफ़र्स के मेमोरी-स्टोरेज क्षेत्र कचरा संग्रहण के अधीन नहीं हैं क्योंकि वे मानक जेवीएम ढेर के बाहर हैं।

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


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

4
यह संदेहास्पद है कि NIO (और मूल संचालन) का वर्णन करने वाली पुस्तक में निश्चितता हो सकती है। आखिरकार, विभिन्न JVM और ऑपरेटिंग सिस्टम चीजों को अलग-अलग तरीके से प्रबंधित करते हैं, इसलिए लेखक को कुछ व्यवहार की गारंटी देने में असमर्थ होने के लिए दोषी नहीं ठहराया जा सकता है।
मार्टिन टस्केविसियस

@RobertKlemme, +1, हम सभी अनुमान लगाने से नफरत करते हैं, हालांकि, सभी प्रमुख OSes के लिए प्रदर्शन को मापना असंभव हो सकता है, क्योंकि अभी भी कई प्रमुख OSes हैं। एक अन्य पोस्ट ने प्रयास किया कि, लेकिन हम बेंचमार्क के साथ कई समस्याओं को देख सकते हैं , जिसकी शुरुआत "ओएस के आधार पर व्यापक रूप से परिणाम में उतार-चढ़ाव" से होती है। इसके अलावा, अगर वहाँ एक काली भेड़ है जो हर I / O पर कॉपी करने वाले बफर की तरह भयानक सामान बनाती है? फिर उस भेड़ की वजह से, हमें उन कोड को लिखने से रोकने के लिए मजबूर किया जा सकता है जो हम अन्यथा उपयोग करेंगे, बस इन सबसे खराब स्थिति से बचने के लिए।
पचेरियर

@RobertKlemme मैं सहमत हूँ। यहाँ बहुत अधिक अनुमान है। उदाहरण के लिए, बाइट सरणियों को आवंटित करने के लिए जेवीएम गायब नहीं है।
लोर्न

@Edwin Dalorzo: हमें वास्तविक दुनिया में ऐसे बाइट बफर की आवश्यकता क्यों है? क्या उन्हें प्रक्रिया के बीच स्मृति साझा करने के लिए हैक के रूप में आविष्कार किया गया है? उदाहरण के लिए कहें कि जेवीएम एक प्रक्रिया पर चलता है और यह एक अन्य प्रक्रिया होगी जो नेटवर्क या डेटा लिंक परत पर चलती है - जो डेटा को प्रसारित करने के लिए जिम्मेदार है - क्या ये बाइट बफ़र्स को इन प्रक्रियाओं के बीच मेमोरी साझा करने के लिए आवंटित किया गया है? कृपया मुझे सही करें अगर मैं गलत हूँ ..
टॉम टेलर

25

Jvm के अंदर पहुंच के लिए प्रत्यक्ष बफ़र्स के तेज़ होने की अपेक्षा करने का कोई कारण नहीं है । उनका फायदा तब होता है जब आप उन्हें देशी कोड में भेजते हैं - जैसे, सभी प्रकार के चैनलों के पीछे का कोड।


वास्तव में। जैसे, जब स्कला / जावा में IO करने की आवश्यकता होती है और एल्गोरिथम प्रसंस्करण के लिए मेमोरी डेटा में बड़े डेटा के साथ एम्बेडेड पायथन / देशी कामों को कॉल करने या डेटा को सीधे Tensorflow में GPU के लिए फ़ीड करने के लिए।
सेमेटिकबेंग

21

चूंकि DirectByteBuffers OS स्तर पर एक डायरेक्ट मेमोरी मैपिंग है

वे नहीं कर रहे हैं वे सिर्फ सामान्य अनुप्रयोग प्रक्रिया मेमोरी हैं, लेकिन जावा जीसी के दौरान स्थानांतरण के अधीन नहीं हैं जो जेएनआई परत के अंदर चीजों को काफी सरल करता है। आप जो वर्णन करते हैं वह लागू होता है MappedByteBuffer

यह कॉल / पुट के साथ तेज प्रदर्शन करेगा

निष्कर्ष प्रीमियर से पीछा नहीं करता है; प्रीमियर गलत है; और निष्कर्ष भी गलत है। जेएनआई की परत के अंदर पहुंचने के बाद वे तेज़ होते हैं, और यदि आप पढ़ रहे हैं और उसी से लिख रहे हैं तो DirectByteBufferवे बहुत तेज़ हैं, क्योंकि डेटा को कभी भी जेएनआई की सीमा पार नहीं करनी है।


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

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

वास्तव में मेरा सारांश गलत कहाँ है? और किस "सारांश" से शुरू करना है? मैं स्पष्ट रूप से "जावा भूमि से संचालन डाल" के बारे में बात कर रहा था। यदि आप केवल चैनल के बीच डेटा की प्रतिलिपि बनाते हैं (अर्थात जावा भूमि में डेटा से कभी भी निपटना नहीं है) तो यह एक अलग कहानी है।
रॉबर्ट क्लेमे

@RobertKlemme आपका कथन कि 'केवल एक सीधी बफ़र [केवल] में जावा भूमि से सभी कार्यों को पार करना है' गलत है। दोनों मिल जाता है और पार करना पड़ता है।
लोर्ने

EJP, आप स्पष्ट रूप से अभी भी वांछित अंतर को याद नहीं कर रहे हैं @RobertKlemme एक वाक्यांश में "ऑपरेशन डाल" शब्दों का उपयोग करके और वाक्य के विपरीत वाक्यांश में "IO संचालन" शब्दों का उपयोग करके बना रहा था। बाद के वाक्यांश में, उनका इरादा बफर और ओएस-प्रदान डिवाइस के बीच संचालन को संदर्भित करना था।
नाकी

18

सबसे अच्छा अपने स्वयं के माप करने के लिए। त्वरित जवाब है कि एक से भेजा जा रहा लगता है allocateDirect()बफर 25% तक 75% कम समय लगता है की तुलना में allocate()विविधता (एक फ़ाइल को कॉपी के रूप में परीक्षण / dev / बातिल के लिए), आकार पर निर्भर करता है, लेकिन आवंटन ही हो सकता है कि काफी (यहां तक कि द्वारा धीमी 100x का एक कारक)।

सूत्रों का कहना है:


धन्यवाद। मैं आपके उत्तर को स्वीकार करूंगा लेकिन मुझे प्रदर्शन में अंतर के बारे में कुछ और विशिष्ट विवरणों की तलाश है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.