星期六, 3月 29, 2008

Java Native Interface - Accessing Array

之前提到JNI在存取陣列時很跛腳, 所以, 這篇就是要記錄JNI怎麼存取陣列. 這邊所指的JNI是指C/C++所寫的source code.

直接切入正題, JNI要怎麼存取從Java傳遞過來的陣列? 以byte為例, 在Java端某個class MyCls有這樣一個method,
private native void passByteArray(byte byteArray[]);
然後在MyCls的某個method裡面
byte buffer[] = new byte[1024 * 1024 * 4]; // 4Mb的陣列
passByteArray(buffer);
然後在JNI是這樣存取陣列的
jsize len = (*jenv)->GetArrayLength(jenv, byteArray);
jbyte *buffer = (*jenv)->GetByteArrayElements(jenv, byteArray, NULL);
// do something
(*jenv)->ReleaseByteArrayElements(jenv, byteArray, buffer, JNI_COMMIT);
跛腳的地方在如果要讓JNI修改的值反映回Java裡的話Get和Release這兩個動作少不了...整個慢慢慢掉...因為Get回傳的指標是指到另一塊一樣大小的記憶體空間副本而不是原本的哪一塊空間, 所以想當然, Release則是把Get回傳的空間再覆寫回去, 順便把空間free掉.

不過, 事實上有另兩種做法...一種用另一組Get和Release函式...另一種是利用java.nio.ByteBuffer
另一組Get和Release函式的寫法如下(用剛才的例子重寫一次):
jsize len = (*jenv)->GetArrayLength(jenv, byteArray);
jbyte *buffer = (*jenv)->GetPrimitiveArrayCritical(jenv, byteArray, NULL);
// do something
(*jenv)->ReleasePrimitiveArrayCritical(jenv, byteArray, buffer, JNI_COMMIT);
為什麼這比較快呢? 因為Critical的Get會儘量, 但不是一定, 回傳原本哪一塊記憶體空間的位置, 所以修改即使不Release還是會反映回Java. 但, 要怎麼知道回傳的是否為另一份copy呢? 答案是利用最後一個引數其型態為*jboolean. 再修改範例一次,
jboolean isCopy = JNI_FALSE;
jsize len = (*jenv)->GetArrayLength(jenv, byteArray);
jbyte *buffer = (*jenv)->GetPrimitiveArrayCritical(jenv, byteArray, &isCopy);
// do something
if (JNI_TRUE == isCopy)
 (*jenv)->ReleasePrimitiveArrayCritical(jenv, byteArray, buffer, JNI_COMMIT);
但根據Java Performance Tuning, Second Edition一書表示, 只要Get就要Release不管是不是取得原本的記憶體空間...無言...不過, 根據我自己寫程式測試結果, Critical的函式還是比較快...但有一個很恐怖的限制, 就是在Critical的Get和Release之間, 因為Java Garbage Collection的關係, 行程不能變成BLOCK狀態...不然...

最後是java.nio.ByteBuffer, 事實上只是利用ByteBuffer.allocateDirect(size)配置記憶體, 然後在JNI的存取是這樣寫的,
jbyte *directBuffer = (*jenv)->GetDirectBufferAddress(jenv, byteBuffer);
jlong directBufferLength = (*jenv)->GetDirectBufferCapacity(jenv, byteBuffer);
// do something
嗯, 這樣就可以了...但, 在Java端就沒辦法把ByteBuffer轉成byte[]存取, 因為一些限制, 不過這倒還OK...這做法可以讓Java和JNI共享一塊記憶體空間, 即然共享就要處理同步問題...

打了一堆, 沒CODE沒真相...最後是附檔包含java source code和c/c++ source code...這檔案是我用來測試效能的....很意外, CRITICAL GET/RELEASE最好..., 快在assignment operation時, TIME(CRITICAL GET的空間) < TIME(java.nio.ByteBuffer)

附檔: JNIDemo.zip

星期日, 3月 16, 2008

最近在?

最近好像也沒做什麼...就是看一看USB 2.0 spec.然後寫了簡單的USB Keyboard/Mouse的程式. 然後稍微看了Java Native Interface (JNI)的做法, 覺得JNI還滿坡腳的. 能不用還是不要用的好. 當然還有一些是不知道能不能公開的寫在blog上, 所以也就不寫了.

為什麼說JNI坡腳? 其實只為了一個理由, 我想在Java或Native Code其中一邊配置出一塊記憶體, 然後供Java和Native Code共同使用. 我如果在Java裡配置憶體空間A並傳遞到Native Code, 在Native Code則很可能必需先產生一份一樣大的記憶體空間B, 然後對這個空間做修改的動作, 最後將B覆寫回A. 這樣做完全失去共享記憶體空間的目的.

下一次寫一篇來介紹JNI吧.