I want to compare the performance difference between using byte[]
and ByteBuffer
in Java, which approach I should use in my production code. I wrote the following Java sandbox code:
public native int MD_GetHsmInfo(int hsmIndex, int infoType, byte[] value);
public native int MD_TestByteBuffer(int hsmIndex, int infoType, ByteBuffer buf);
//inside main()
byte[] arr = new byte[64];
h.MD_GetHsmInfo(2, 0, arr);
System.out.println(">" + new String(arr) + "<");
ByteBuffer buf = ByteBuffer.allocateDirect(64);
h.MD_TestByteBuffer(2, 1, buf);
System.out.println(">" + StandardCharsets.UTF_8.decode(buf).toString() + "<");
And then I have my C code - this is the one I need help with:
JNIEXPORT jint JNICALL Java_com_sprint_jni_JNISandbox_MD_1GetHsmInfo
(JNIEnv *env, jobject obj, jint hsmIndex, jint infoType, jbyteArray array)
{
//printf("This is MD_GetHsmInfo()\n");
jboolean isCopy;
jsize len = (*env)->GetArrayLength(env, array);
jbyte* bp = (*env)->GetByteArrayElements(env, array, &isCopy);
if (!bp) {
// exception handling here
return 0;
}
jint ret = MD_GetHsmInfo(hsmIndex, infoType, bp, len);
int mode = 0;
// if error code is returned then do not save changes
if(ret != 0) {
mode = JNI_ABORT;
}
(*env)->ReleaseByteArrayElements(env, array, bp, mode);
return ret;
}
JNIEXPORT jint JNICALL Java_com_sprint_jni_JNISandbox_MD_1TestByteBuffer
(JNIEnv *env, jobject obj, jint hsmIndex, jint infoType, jobject buf)
{
jbyte *bp = (*env)->GetDirectBufferAddress(env, buf);
jint len = (*env)->GetDirectBufferCapacity(env, buf);
//printf("length of buffer = %d", len);
jint ret = MD_GetHsmInfo(hsmIndex, infoType, bp, len);
return ret;
}
Both functions seem to work fine, but I would like to make both functions production-ready, so that I can demonstrate without bias which approach to use. As I am not too familiar with JNI, it is likely that my code is not written optimally, so I would appreciate any feedback on that too.
Background
In the above snippet, MD_GetHsmInfo
is a C function which my Java code needs to call.
MD_RV MD_GetHsmInfo(uint32_t hsmIndex, MD_Info_t infoType, void *pValue, uint32_t valueLen);
pValue
is described as such:
The address of the buffer to hold the information value. The buffer must have been allocated by the caller. The size of the buffer is determined by the infoType that is being obtained.
MD_RV
and MD_Info_t
are enums defined as follows:
typedef enum {
MDI_HSM_DESCRIPTION = 1,
//...so on
} MD_Info_t;
// similar implementation for MD_RV
The actual implementation of the function resides within a DLL file, but I do not have access to it yet. For now I have written a dummy implementation as follows:
int MD_GetHsmInfo(int hsmIndex, int infoType, void *pValue, int valueLen) {
//printf("This is dummy implementation of MD_GetHsmInfo()\n");
char *str;
if(infoType == 1) {
str = "info = 1";
} else if(infoType == 2) {
str = "info = 2";
} else {
str = "no info";
}
jbyte *jb = (jbyte*)pValue;
while(*str) {
*jb++ = *str++;
}
*jb = 0;
valueLen = strlen(str);
return 0;
}
-
\$\begingroup\$ If you had to choose, are you more interested in a comparison between approaches or a review of this specific code? \$\endgroup\$Mast– Mast ♦2019年07月30日 08:36:43 +00:00Commented Jul 30, 2019 at 8:36
-
\$\begingroup\$ @Mast I'm not that familiar with JNI and I came up with this code entirely based on what I read online. I am more interested in a comparison, but at the same time I would also like to know how I can improve my current one. Does this make sense? \$\endgroup\$user10931326– user109313262019年07月30日 08:58:47 +00:00Commented Jul 30, 2019 at 8:58
-
\$\begingroup\$ @user10931326 perhaps you should mention that you're interested in performance. \$\endgroup\$Antti Haapala– Antti Haapala2019年07月30日 09:33:14 +00:00Commented Jul 30, 2019 at 9:33
2 Answers 2
Minor issues in MD_GetHsmInfo()
valueLen = strlen(str);
serves no purpose below as the value assigned to valueLen
is not used.
int MD_GetHsmInfo(int hsmIndex, int infoType, void *pValue, int valueLen) {
...
valueLen = strlen(str);
return 0;
}
Perhaps
int MD_GetHsmInfo(int hsmIndex, int infoType, void *pValue, int *valueLen) {
...
*valueLen = strlen(str);
return 0;
}
or
int MD_GetHsmInfo(int hsmIndex, int infoType, void *pValue, size_t *valueLen) {
Cast not needed
int MD_GetHsmInfo(int hsmIndex, int infoType, void *pValue, int valueLen) {
...
// jbyte *jb = (jbyte*)pValue;
jbyte *jb = pValue;
Term "production ready" certainly has different meanings for different people, but I consider it as a reason to do some nitpicking, at least.
First of all, you mentioned that the actual goal is to compare the performance between byte[]
arrays and ByteBuffer
. This is something that one could write an essay on, and it's tremendously difficult to pull objective results out of such a test. In order to not distort the results, you'd have to run the test with different array/buffer sizes, consider the possible effects of GC and pinning of the JVM (which is somewhat unspecified, and thus, could only be evaluated empirically for one given VM). You'd also have to use a dummy implementation of MD_GetHsmInfo
(as you already did), in order to not distort the results.
(Actually, I should have some idea about the performance difference here and the results that you could expect, but have to admit that I haven't yet evaluated this as systematically as I probably should have. Maybe I'll try to allocate some time for that, and extend this answer with some results later...)
You're talking about ByteBuffer
in the text, but the code seems to be targeting direct ByteBuffer
specifically. This may look like a detail, but when you say that the code should be "production ready", you have to be aware that GetDirectBufferAddress
may return NULL
when the buffer is not a direct ByteBuffer
. That is, it will crash when you call your function with a ByteBuffer
that was created by calling ByteBuffer.wrap(someByteArray)
.
You're creating the isCopy
variable and passing it to GetByteArrayElements
, but you are not using the value of this variable. Alternatively, you can simply pass in NULL
as the last parameter. Whether or not the returned object is a copy is mainly/only relevant when you release the array elements later.
As explained in ReleaseByteArrayElements
, the mode
that is passed in as the last parameter does only have an effect if the returned elements have been a copy. But using 0
as the default and JNI_ABORT
in case of an error should be fine.
When the goal of the comparison is about performance, then the *PrimitiveArrayCritical
family of methods may be worth being mentioned: They can (roughly speaking) be used to treat the section that uses the array data as a "critical section", and temporarily disable the GC, making it more likely that the VM will not have to create a copy of the data that later has to be written back.
But again, the details here are not directly specified and certainly depend on the VM and the size of the array, so it's hard or impossible to say which effect this may have on performance in practice.