无论如何,当您需要/拥有数据作为本机java数组时,SWIG效率为carrays.i vs arrays_java.i

时间:2012-10-02 16:18:31

标签: java swig

我很好奇它在swig docs中所说的关于swig / java中处理数组的两种基本方法的效率。特别是,我想知道如果你最终需要复制到本机java数组/从本机java数组中复制carrays.i的方式是否真的更有效?例如。说我有一个C函数void populate(int x [])来调用然后我需要将结果传递给一个带有本机java int []的Java函数。为了做到这一点,我需要:

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

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

然后复制到本机java数组:

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

然后调用我的原生java函数,它接受一个原生的int []

f(nativeArray);

这是否真的比

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

因为在前一种情况下,你还是要做副本吗?

1 个答案:

答案 0 :(得分:3)

假设如果你正在使用carrays.i那么你也不是在Java数组之间来回复制 - 你想在任何地方使用carrays.i类型而不是那个。

仍有两种类型的开销可以衡量 - JNI调用会产生一定的惩罚并复制另一个惩罚。使用carray.i,后者保持低价,因为您正确识别了更多JNI呼叫的价格。另外,对Get<PrimitiveType>ArrayElements的调用也可能会引入一个副本,具体取决于JVM。

然而,令人惊讶的是,arrays_java.i的实现会产生比您预期更多的副本,例如,类型映射使用的SWIG_JavaArrayIn函数包含以下内容:

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

并且在输出类型映射中也发生了类似的相应副本(通过赋值):

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

(这是JVM可能复制的补充!)

然而,所有这些的推理都是合理的 - 这些类型图必须支持“奇怪”的情况,其中使用的JNITYPE并不完全映射到C类型。可能出现这种情况的一个示例是unsigned char数组 - Java中最接近的类型是byte,但是byte是有符号的,以便表示数组的值范围C中的unsigned char s将在Java端显示为short的数组。由于sizeof(jshort) != sizeof(unsigned char)内存布局不兼容,因此需要复制并填写并返回。

如果这困扰你(我强烈建议使用基准测试),那么仍然可以编写自己的高效字体图,它们使用您希望的JNI调用,例如:

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

将指针传递给从VM获取的Java数组(可能是或者可能不是副本,您可以使用可选的第三个参数检查GetIntArrayElements这是一个布尔值,指示JVM是否创建了是否在过程中复制。)

这些类型映射以“原样”方式传递Java数组,然后从JVM获取指针以在C函数中使用。如果函数成功,则调用ReleaseIntArrayElements,第三个参数为0 - 这确保了任何修改在Java中也是可见的。如果调用不成功,那么将使用JNI_ABORT调用该函数,如果返回的指针是副本,则不会显示任何更改。

我们可以称之为:

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

可能有0个副本,但只能在Java中的类型与C中相应类型完全匹配的情况下使用。