# Production-ready comparison between byte-array and ByteBuffer

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;
}

• If you had to choose, are you more interested in a comparison between approaches or a review of this specific code? – Mast Jul 30 '19 at 8:36
• @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? – user10931326 Jul 30 '19 at 8:58
• @user10931326 perhaps you should mention that you're interested in performance. – Antti Haapala Jul 30 '19 at 9:33

## 2 Answers

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.