
जावा प्रोग्रामर मूल तरीकों का सहारा क्यों लेते हैं? कभी-कभी, तृतीय-पक्ष DLL लाइब्रेरी का उपयोग करने के लिए। अन्य मामलों में, अनुकूलित C या कोडांतरक कोड के कारण महत्वपूर्ण एल्गोरिथ्म को गति देने के लिए। उदाहरण के लिए, स्ट्रीमिंग मीडिया के प्रसंस्करण के लिए, संपीड़न, एन्क्रिप्शन आदि के लिए।
लेकिन मूल विधि को कॉल करना मुफ्त नहीं है। कई बार, JNI का ओवरहेड प्रदर्शन लाभ से भी अधिक होता है। और सभी क्योंकि वे शामिल हैं:
- स्टैक फ्रेम बनाना;
- एबीआई के अनुसार स्थानांतरण तर्क;
jobject
हैंडल ( jobject
) में रैपिंग लिंक;JNIEnv*
और jclass
को अतिरिक्त तर्क पारित jclass
;- मॉनिटर की कब्जा और रिहाई अगर
synchronized
विधि; - देशी कार्य को जोड़ने वाला "आलसी";
- विधि के प्रवेश और निकास का पता लगाना;
in_Java
राज्य से in_native
और इसके विपरीत में एक धारा का हस्तांतरण;- सफ़ाई के लिए जाँच करें;
- संभावित अपवादों को संभालना।
लेकिन अक्सर देशी तरीके सरल होते हैं: वे अपवाद नहीं फेंकते हैं, ढेर में नई वस्तुओं का निर्माण नहीं करते हैं, स्टैक को बायपास नहीं करते हैं, हैंडल के साथ काम नहीं करते हैं और सिंक्रनाइज़ नहीं हैं। क्या उनके लिए यह संभव है कि वे अनावश्यक क्रियाएं न करें?
हां, और आज मैं सरल JNI विधियों को तेजी से कॉल करने के लिए हॉटस्पॉट JVM की अनिर्दिष्ट सुविधाओं के बारे में बात करूंगा। हालांकि यह अनुकूलन जावा 7 के पहले संस्करणों के बाद से सामने आया है, जो आश्चर्यजनक है, किसी ने भी इसके बारे में कहीं नहीं लिखा है।
जेएनआई जैसा कि हम उसे जानते हैं
उदाहरण के लिए, एक साधारण मूल विधि पर विचार करें जो
byte[]
प्राप्त करता है
byte[]
सरणी और तत्वों का एक योग देता है। JNI में एक सरणी के साथ काम करने के कई तरीके हैं:
GetByteArrayRegion
- जावा सरणी तत्वों को मूल मेमोरी में निर्दिष्ट स्थान पर कॉपी करता है;
GetByteArrayRegion उदाहरण JNIEXPORT jint JNICALL Java_bench_Natives_arrayRegionImpl(JNIEnv* env, jclass cls, jbyteArray array) { static jbyte buf[1048576]; jint length = (*env)->GetArrayLength(env, array); (*env)->GetByteArrayRegion(env, array, 0, length, buf); return sum(buf, length); }
GetByteArrayElements
समान है, केवल JVM ही उन मेमोरी क्षेत्र को आवंटित करता है जहां तत्वों की प्रतिलिपि बनाई जाएगी। सरणी के साथ काम पूरा होने पर, आपको रिलीज़बाइटएयरएयरलाइंस को कॉल करना होगा।
GetByteArrayElements उदाहरण JNIEXPORT jint JNICALL Java_bench_Natives_arrayElementsImpl(JNIEnv* env, jclass cls, jbyteArray array) { jboolean isCopy; jint length = (*env)->GetArrayLength(env, array); jbyte* buf = (*env)->GetByteArrayElements(env, array, &isCopy); jint result = sum(buf, length); (*env)->ReleaseByteArrayElements(env, array, buf, JNI_ABORT); return result; }
- क्यों, आप पूछते हैं, सरणी की एक प्रति बनाते हैं? लेकिन आप मूल से सीधे जावा हीप में वस्तुओं के साथ काम नहीं कर सकते हैं, क्योंकि वे कचरा कलेक्टर द्वारा सीधे ले जाया जा सकता है जबकि जेएनआई विधि चल रही है। हालाँकि, एक
GetPrimitiveArrayCritical
फ़ंक्शन है, जो हीप में सरणी का सीधा पता देता है, लेकिन ReleasePrimitiveArrayCritical
कॉल करने से पहले GC को काम करने से ReleasePrimitiveArrayCritical
।
GetPrimitiveArrayCritical उदाहरण JNIEXPORT jint JNICALL Java_bench_Natives_arrayElementsCriticalImpl(JNIEnv* env, jclass cls, jbyteArray array) { jboolean isCopy; jint length = (*env)->GetArrayLength(env, array); jbyte* buf = (jbyte*) (*env)->GetPrimitiveArrayCritical(env, array, &isCopy); jint result = sum(buf, length); (*env)->ReleasePrimitiveArrayCritical(env, array, buf, JNI_ABORT); return result; }
गंभीर मूल निवासी
और यहाँ हमारा गुप्त उपकरण है। बाह्य रूप से, यह एक नियमित 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); }
जैसा कि आप देख सकते हैं, कार्यान्वयन में कुछ भी नहीं है।
लेकिन क्रिटिकल नेटिव बनने की विधि के लिए, इसे सख्त प्रतिबंधों को पूरा करना होगा:
- विधि
static
होनी चाहिए और synchronized
नहीं होनी चाहिए; - तर्कों के बीच, केवल आदिम प्रकार और प्राथमिकताओं के सरणियों का समर्थन किया जाता है;
- क्रिटिकल नेटिव जेएनआई फ़ंक्शंस को कॉल नहीं कर सकता है, इसलिए, जावा ऑब्जेक्ट्स को आवंटित करें या अपवाद फेंक दें;
- और, सबसे महत्वपूर्ण बात, विधि थोड़े समय में पूरी होनी चाहिए, क्योंकि यह जीसी को रनटाइम पर रोकती है।
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 के साथ पेश किया गया है। कुछ नियमों के अनुसार जेएनआई जैसे फ़ंक्शन को लागू करके, आप मूल विधि को कॉल करने और मूल कोड में जावा सरणियों को संसाधित करने के ओवरहेड को काफी कम कर सकते हैं। हालांकि, लंबे समय तक चलने वाले कार्यों के लिए ऐसा समाधान काम नहीं करेगा, क्योंकि जीसी क्रिटिकल नेटिव चल रहा है, जबकि जीसी शुरू नहीं हो पाएगा।