2

我很好奇 swig文档中关于在 swig/java 中处理数组的两种基本方法的效率。特别是,我想知道如果您最终需要复制到本机 java 数组或从本机 java 数组复制,那么 carrays.i 方式是否真的更有效?例如,假设我有一个 C func void populate(int x[]) 要调用,然后我需要将结果传递给一个采用本机 java int[] 的 Java func。要做到这一点,我需要:

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

因为在前一种情况下,无论如何你都必须做副本?

4

1 回答 1

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 中的s 数组将在 Java 端unsigned char公开为数组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 中的相应类型完全匹配的情况下使用。

于 2012-10-07T10:27:51.057 回答