kb1ooo
kb1ooo

Reputation: 8882

SWIG Efficiency of carrays.i vs arrays_java.i when you need/have data as native java array anyway

I'm curious about what it says in the swig docs regarding efficiency of the 2 fundamental ways of dealing with arrays in swig/java. In particular, I'm wondering if the carrays.i way is really more efficient if you end up needing to copy to/from a native java array anyway? E.g. say I have a C func void populate(int x[]) to call and then I needed to pass the result to a Java func that takes a native java int[]. To do it the carrays way, I'd need to:

%include "carrays.i"
%array_class(int, intArray);

intArray array = new intArray(10000000);
populate(array);

then copy to a native java array:

int[] nativeArray = new int[10000000];
for(int i = 0; i < 10000000; ++i)
{
    nativeArray[i] = array.getitem(i);
} 

then call my native java function which takes a native int[]

f(nativeArray);

Is that really more efficient than

%include "arrays_java.i"
int[] nativeArray = new int[10000000];
populate(nativeArray);
f(nativeArray);

since in the former case, you have to do the copy anyway?

Upvotes: 2

Views: 1185

Answers (1)

The assumption is that if you're using carrays.i then you're not copying back and forth between a Java array also - you'd want to use the carrays.i type everywhere instead of that.

There are still two types of overhead you can expect to measure - JNI calls incur a certain penalty and copies another penalty. Using carray.i, the latter is kept low for the price of more JNI calls as you correctly identified. Additionally calls to Get<PrimitiveType>ArrayElements may also introduce a copy depending on the JVM.

What might be surprising however is that the implementation of arrays_java.i makes more copies than you would otherwise expect, for example the SWIG_JavaArrayIn functions used by the typemaps contain the following:

  for (i=0; i<sz; i++)
    JAVA_TYPEMAP_ARRAY_ELEMENT_ASSIGN(CTYPE)

and there's a similar corresponding copy (via assignment) happening in the output typemaps too:

  for (i=0; i<sz; i++)
    arr[i] = (JNITYPE)result[i];

(This is in addition to the JVM having possibly made a copy!)

The reasoning for all this is sound however - these typemaps have to support "odd" cases where the JNITYPE being used doesn't exactly map onto the C type. An example of where this might occur would be with an array of unsigned char - the closest type in Java is byte, however byte is signed so in order to represent the range of values an array of unsigned chars in C would be exposed as an array of short on the Java side. Since sizeof(jshort) != sizeof(unsigned char) the memory layout isn't compatible and the copy is neccessary to fill in and return.

If this bothers you (I'd strongly recommend benchmarking) then it's still possible to write your own, efficient typemaps, which use the JNI calls as you seem to hope, for example:

%module test

%typemap(jtype) int arr[ANY] "int[]"
%typemap(jstype) int arr[ANY] "int[]"
%typemap(jni) int arr[ANY] "jintArray"
%typemap(javain) int arr[ANY] "$javainput"
%typemap(in) int arr[ANY] {
  // check the size is compatible here also
  $1 = JCALL2(GetIntArrayElements, jenv, $input, 0);
}
%typemap(freearg) int arr[ANY] {
  if ($1) {
    JCALL3(ReleaseIntArrayElements, jenv, $input, $1, JNI_ABORT);
  }
}
%typemap(argout) int arr[ANY] {
  JCALL3(ReleaseIntArrayElements, jenv, $input, $1, 0);
  $1 = NULL;
}

%inline %{
void populate(int arr[100000]) {
  for (unsigned i = 0; i < 100000; ++i) {
    arr[i] = -i;
  }
}
%}

passes the pointer to the Java array obtained from the VM (which may or may not be a copy still, you can check with the optional third argument to GetIntArrayElements which is a boolean that indicates if the JVM made a copy in the process or not).

These typemaps pass the Java array in "as is" and then obtain a pointer from the JVM to use within the C function. If the function is successful then ReleaseIntArrayElements is called with the third parameter as 0 - this ensures that any modifications are visible within Java also. If the call is unsuccessful then the function will be called with JNI_ABORT which doesn't make any changes visible in the case where the pointer given back was a copy.

We can call that as:

public class run {
  public static void main(String[] argv) {
    System.loadLibrary("test");
    int[] arr = new int[100000];
    test.populate(arr);
    // Only print 40 to avoid spamming my screen!
    for (int i = 0; i < 40; ++i) {
      System.out.println(arr[i]);
    }
  }
}

which potentially has 0 copies but can only be used where your types in Java are an exact match for the corresponding type in C.

Upvotes: 3

Related Questions