2

假设我有一个包含 N 个元素的 C 字符串数组。我的目标是使用 JNI 将该数组传递给 Java 函数,并将一个等长的新字符串数组返回给 C 空间。目前我正在做以下事情:

  • 使用 NewObjectArray 生成一个长度为 N 的 Java Object 数组。
  • 调用 NewStringUTF/SetObjectArray N 次,将每个单独的 C 字符串装箱到 Java 对象数组中。
  • 调用 copyStrArr(来源如下)。
  • 使用 malloc 分配长度为 N 的 (char *) 数组。
  • 调用 GetObjectArrayElement/GetStringUTFChars N 次,从返回的 Java 对象数组中拆箱每个单独的 Java 字符串。

作为参考,Java 代码如下所示:

public static String[] copyStrArr(String []inArr)
{
    String []outArr = new String[inArr.length];
    for(int _i = 0; _i < outArr.length; _i++) {
        outArr[_i] = inArr[_i]; /* Normally real work would be done here */
    }
    return outArr;
}

在“真实”情况下,实际工作将在 for 循环内完成,但为了进行基准测试,我们只是制作数据的副本。

对于较大的 N 值,这很慢。慢得不得了。当将类似大小的整数或双精度数组从 C 移动到 Java 并返回时,它的运行速度比 String[] 案例快约 70 倍。大约 99.5% 的时间用于装箱和拆箱数据。在原始情况下,JNI 提供 {Set,Get}ArrayRegion 函数将原始数组从 C 空间批量复制到 Java 空间并返回,这要快得多。

有人建议我使用 byte[] 作为中介将数据放入 Java 空间,然后在 Java 中进行单独的字符串对象装箱(JVM 可以在其中优化事物)。基准测试表明,它的性能比原始测试稍差,将大部分开销转移到 Java 中。部分原因可能是我可能没有在 Java 中对 byte[] 进行最佳拆箱/装箱。我正在执行以下操作:

  • 使用 NewByteArray 分配足够大的 byte[]
  • 调用 SetByteArrayRegion N 次来填充 byte[]
  • 调用 copyBytArray(来源如下)
  • 调用 GetByteArrayRegion 并将整个结果复制回 C 空间
  • 分配足够大的 (char *) 数组
  • 将结果中的 N 个字符串中的每一个复制到新分配的数组中。

我的 Java 代码如下所示:

public static byte[] copyBytArr(byte []inArr)
{
    String[] tokInArr = new String(inArr, UTF8_CHARSET).split("\0");
    String []tokOutArr = new String[tokInArr.length];
    int len = 0;
    for(int _i = 0; _i < tokOutArr.length; _i++) {
        tokOutArr[_i] = tokInArr[_i]; /* Normally real work would be done here */
        len += (tokInArr[_i].length() + 1);
    }
    byte[] outArr = new byte[len];
    int _j = 0;
    for(int _i = 0; _i < tokOutArr.length; _i++) {
        byte[] bytes = tokOutArr[_i].getBytes(UTF8_CHARSET);
        for(int _k = 0; _k < bytes.length; _k++) {
            outArr[_j++] = bytes[_k];
        }
        outArr[_j++] = '\0';
    }
    return outArr;
}

在这个测试中,大约 55% 的开销用于 Java,其余用于装箱/拆箱。

有人建议我的一些开销与我在 C 中使用 UTF-8 数据这一事实有关,因为 Java 使用 UTF-16。这是不可避免的。

有人对我如何更有效地解决这个问题有任何想法吗?

4

1 回答 1

1

我认为您的问题是分配了许多字符串对象。为了获得真正的性能,您只需交换大字节 [] 并使用包装类“指向”字节数组进行字符串处理。只要您从 C chars[] 来回创建字符串对象,就不会获得真正的吞吐量。

FST 正在做与“StructString”类类似的操作来操作 byte[] 数据,而无需创建“真实”对象。

为了进一步加快数据交换,您可能希望使用内存映射文件创建共享内存,并通过 Unsafe 或 ByteBuffers 访问它。

于 2013-10-22T16:49:22.807 回答