क्या देशी तरीका महंगा है? जेएनआई सीक्रेट एक्सटेंशन


जावा प्रोग्रामर मूल तरीकों का सहारा क्यों लेते हैं? कभी-कभी, तृतीय-पक्ष DLL लाइब्रेरी का उपयोग करने के लिए। अन्य मामलों में, अनुकूलित C या कोडांतरक कोड के कारण महत्वपूर्ण एल्गोरिथ्म को गति देने के लिए। उदाहरण के लिए, स्ट्रीमिंग मीडिया के प्रसंस्करण के लिए, संपीड़न, एन्क्रिप्शन आदि के लिए।

लेकिन मूल विधि को कॉल करना मुफ्त नहीं है। कई बार, JNI का ओवरहेड प्रदर्शन लाभ से भी अधिक होता है। और सभी क्योंकि वे शामिल हैं:
  1. स्टैक फ्रेम बनाना;
  2. एबीआई के अनुसार स्थानांतरण तर्क;
  3. jobject हैंडल ( jobject ) में रैपिंग लिंक;
  4. JNIEnv* और jclass को अतिरिक्त तर्क पारित jclass ;
  5. मॉनिटर की कब्जा और रिहाई अगर synchronized विधि;
  6. देशी कार्य को जोड़ने वाला "आलसी";
  7. विधि के प्रवेश और निकास का पता लगाना;
  8. in_Java राज्य से in_native और इसके विपरीत में एक धारा का हस्तांतरण;
  9. सफ़ाई के लिए जाँच करें;
  10. संभावित अपवादों को संभालना।

लेकिन अक्सर देशी तरीके सरल होते हैं: वे अपवाद नहीं फेंकते हैं, ढेर में नई वस्तुओं का निर्माण नहीं करते हैं, स्टैक को बायपास नहीं करते हैं, हैंडल के साथ काम नहीं करते हैं और सिंक्रनाइज़ नहीं हैं। क्या उनके लिए यह संभव है कि वे अनावश्यक क्रियाएं न करें?

हां, और आज मैं सरल JNI विधियों को तेजी से कॉल करने के लिए हॉटस्पॉट JVM की अनिर्दिष्ट सुविधाओं के बारे में बात करूंगा। हालांकि यह अनुकूलन जावा 7 के पहले संस्करणों के बाद से सामने आया है, जो आश्चर्यजनक है, किसी ने भी इसके बारे में कहीं नहीं लिखा है।

जेएनआई जैसा कि हम उसे जानते हैं


उदाहरण के लिए, एक साधारण मूल विधि पर विचार करें जो byte[] प्राप्त करता है byte[] सरणी और तत्वों का एक योग देता है। JNI में एक सरणी के साथ काम करने के कई तरीके हैं:

गंभीर मूल निवासी


और यहाँ हमारा गुप्त उपकरण है। बाह्य रूप से, यह एक नियमित JNI विधि की तरह दिखता है, केवल JavaCritical_ बजाय JavaCritical_ उपसर्ग के साथ। तर्कों के बीच, JNIEnv* और jclass , और jbyteArray बजाय, दो तर्क दिए गए हैं: jint length - सरणी की लंबाई और jbyte* data - सरणी के तत्वों के लिए एक कच्चा सूचक। इस प्रकार, क्रिटिकल नेटिव विधि को महंगे GetArrayLength फ़ंक्शन GetArrayLength और GetByteArrayElements को कॉल करने की आवश्यकता नहीं है - आप तुरंत सरणी के साथ काम कर सकते हैं। इस पद्धति की अवधि के लिए, जीसी में देरी होगी।

 JNIEXPORT jint JNICALL JavaCritical_bench_Natives_javaCriticalImpl(jint length, jbyte* buf) { return sum(buf, length); } 

जैसा कि आप देख सकते हैं, कार्यान्वयन में कुछ भी नहीं है।
लेकिन क्रिटिकल नेटिव बनने की विधि के लिए, इसे सख्त प्रतिबंधों को पूरा करना होगा:

JDK के मूल में क्रिप्टोग्राफिक फ़ंक्शंस के कॉल को तेज करने के लिए क्रिटिकल नेटिव्स को एक निजी हॉटस्पॉट एपीआई के रूप में कल्पना की गई थी। विवरण से जो अधिकतम पाया जा सकता है वह बगट्रैकर में कार्य पर टिप्पणी है । एक महत्वपूर्ण विशेषता: JavaCritical_ फ़ंक्शन को केवल हॉट (संकलित) कोड से कहा जाता है, इसलिए, JavaCritical_ कार्यान्वयन के अलावा, विधि में एक "फॉलबैक" पारंपरिक JavaCritical_ कार्यान्वयन भी होना चाहिए। हालांकि, अन्य JVM के साथ संगतता के लिए यह और भी बेहतर है।

ग्राम में कितने होंगे?


आइए मापें कि विभिन्न लंबाई के सरणियों पर बचत क्या है: 16, 256, 4KB, 64KB और 1MB। स्वाभाविक रूप से जेएमएच का उपयोग करना
बेंचमार्क
 @State(Scope.Benchmark) public class Natives { @Param({"16", "256", "4096", "65536", "1048576"}) int length; byte[] array; @Setup public void setup() { array = new byte[length]; } @GenerateMicroBenchmark public int arrayRegion() { return arrayRegionImpl(array); } @GenerateMicroBenchmark public int arrayElements() { return arrayElementsImpl(array); } @GenerateMicroBenchmark public int arrayElementsCritical() { return arrayElementsCriticalImpl(array); } @GenerateMicroBenchmark public int javaCritical() { return javaCriticalImpl(array); } static native int arrayRegionImpl(byte[] array); static native int arrayElementsImpl(byte[] array); static native int arrayElementsCriticalImpl(byte[] array); static native int javaCriticalImpl(byte[] array); static { System.loadLibrary("natives"); } } 
परिणाम
 Java(TM) SE Runtime Environment (build 1.7.0_51-b13) Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode) Benchmark (length) Mode Samples Mean Mean error Units b.Natives.arrayElements 16 thrpt 5 7001,853 66,532 ops/ms b.Natives.arrayElements 256 thrpt 5 4151,384 89,509 ops/ms b.Natives.arrayElements 4096 thrpt 5 571,006 5,534 ops/ms b.Natives.arrayElements 65536 thrpt 5 37,745 2,814 ops/ms b.Natives.arrayElements 1048576 thrpt 5 1,462 0,017 ops/ms b.Natives.arrayElementsCritical 16 thrpt 5 14467,389 70,073 ops/ms b.Natives.arrayElementsCritical 256 thrpt 5 6088,534 218,885 ops/ms b.Natives.arrayElementsCritical 4096 thrpt 5 677,528 12,340 ops/ms b.Natives.arrayElementsCritical 65536 thrpt 5 44,484 0,914 ops/ms b.Natives.arrayElementsCritical 1048576 thrpt 5 2,788 0,020 ops/ms b.Natives.arrayRegion 16 thrpt 5 19057,185 268,072 ops/ms b.Natives.arrayRegion 256 thrpt 5 6722,180 46,057 ops/ms b.Natives.arrayRegion 4096 thrpt 5 612,198 5,555 ops/ms b.Natives.arrayRegion 65536 thrpt 5 37,488 0,981 ops/ms b.Natives.arrayRegion 1048576 thrpt 5 2,054 0,071 ops/ms b.Natives.javaCritical 16 thrpt 5 60779,676 234,483 ops/ms b.Natives.javaCritical 256 thrpt 5 9531,828 67,106 ops/ms b.Natives.javaCritical 4096 thrpt 5 707,566 13,330 ops/ms b.Natives.javaCritical 65536 thrpt 5 44,653 0,927 ops/ms b.Natives.javaCritical 1048576 thrpt 5 2,793 0,047 ops/ms 


यह पता चला है कि छोटे सरणियों के लिए जेएनआई कॉल की लागत स्वयं विधि के चलने के समय से कई गुना अधिक है! सैकड़ों बाइट्स के सरणियों के लिए, ओवरहेड उपयोगी कार्य के लिए तुलनीय है। खैर, और बहु-किलोबाइट सरणियों के लिए, कॉल करने का तरीका इतना महत्वपूर्ण नहीं है - सभी समय वास्तव में प्रसंस्करण पर खर्च किया जाता है।

निष्कर्ष


क्रिटिकल नेटिव्स हॉटस्पॉट में एक निजी जेएनआई एक्सटेंशन है, जिसे JDK 7 के साथ पेश किया गया है। कुछ नियमों के अनुसार जेएनआई जैसे फ़ंक्शन को लागू करके, आप मूल विधि को कॉल करने और मूल कोड में जावा सरणियों को संसाधित करने के ओवरहेड को काफी कम कर सकते हैं। हालांकि, लंबे समय तक चलने वाले कार्यों के लिए ऐसा समाधान काम नहीं करेगा, क्योंकि जीसी क्रिटिकल नेटिव चल रहा है, जबकि जीसी शुरू नहीं हो पाएगा।

Source: https://habr.com/ru/post/In222997/


All Articles