java - 在没有 toArray 的情况下提高 JNA 性能

标签 java c jna

我正在尝试使用 JNA 创建 C 库到我的 Java 代码的绑定(bind),但我的性能很差。

这是C头文件

struct facet_fin_s {
 int facet;
 int fin;
};
typedef struct facet_fin_s facet_fin_t;

struct tab_facet_fin_s {
 facet_fin_s *data;
 int length;
};
typedef struct tab_facet_fin_s tab_facet_fin_t;

struct facet_s{
 int number_of_fins;
 tab_facet_fin_s tab_facet_fin;
};
typedef struct facet_s facet_t;

extern "C" __declspec(dllexport) void getFins(facet_t* const );

这是C文件
void getFins(facet_t* const facet)
{
    facet->number_of_fins = 258246;
    facet->tab_facet_fin.length = facet->number_of_fins;
    facet->tab_facet_fin.data = (facet_fin_s*)malloc(sizeof(facet_fin_s) * facet->tab_facet_fin.length);
    memset(facet->tab_facet_fin.data, 0, sizeof(facet_fin_s) * facet->tab_facet_fin.length);

    int loop = 0;
    for (loop=0; loop<facet->tab_facet_fin.length; loop++)
    {
        facet_fin_s fin;
        fin.facet = loop;
        fin.fin = loop;
        facet->tab_facet_fin.data[loop] = fin;
    }
}

最后是我在 Java 中的测试
facet_s retFacet = new facet_s();

TestJNABindingLibrary.getFins(retFacet);

Structure facetFin[] = retFacet.tab_facet_fin.data.toArray(retFacet.tab_facet_fin.length);

for (int i = 0; i < facetFin.length; i++)
{
    System.out.println(((facet_fin_s)facetFin[i]).fin);
    System.out.println(((facet_fin_s)facetFin[i]).facet);
}

我的函数getFins返回的结果是正确的,但是操作真的很慢。
我认为在 retFacet.tab_facet_fin.data 上调用“toArray”需要 38 秒!

我认为 JNA 花费了太多时间来将 Java 结构与 native 结构同步并复制数据。

我尝试了 Byte 数组和 ByteBuffer 直接访问内存而不复制,但是这些方法对于原始对象而不是结构体来说很方便。我还尝试使用指针轻松访问数据,但没有任何成功。

我的目标是找到一种方法来提高性能,同时保持 Java 代码清晰且易于操作(我将在项目中有很多这样的功能)。有什么方法可以通过 JNA 实现吗? (我已经考虑过 JNI、SWIG 和 BridJ..)。欢迎使用一些代码;-)

谢谢

编辑

这是我尝试禁用自动同步和读取字段
facet_s retFacet = new facet_s();
retFacet.setAutoSynch(false);
TestJNABindingLibrary.getFins(retFacet);
facet_fin_s[] fins = (facet_fin_s[])retFacet.tab_facet_fin.readField("data");

不幸的是,fins似乎是 null
编辑 2

Technomage 告诉我必须阅读 tab_facet_fin第一的。但我仍然无法将结果作为数组。
tab_facet_fin_s tab = (tab_facet_fin_s)retFacet.readField("tab_facet_fin");
facet_fin_s[] fins = (facet_fin_s[])tab.readField("data");

引发强制转换异常。有没有简单的方法来阅读这个领域?

编辑 3

感谢 Technomage,我在 readField 上进行了完整的尝试战略。有两种获取数据的方法,取决于 dataPointerStructure.ByReference .

这是共同的部分(每个java类在其构造函数中调用setAutoSynch(false))
facet_s retFacet = new facet_s();
TestJNABindingLibrary.getFins(retFacet);

然后 Pointer案件
int length = (int)retFacet.readField("number_of_fins");
tab_facet_fin_s tab = (tab_facet_fin_s)retFacet.readField("tab_facet_fin");
int[] data = new int[length*2];
tab.data.read(0, data, 0, data.length);
for (int i = 0; i < data.length; i++)
{
   System.out.println(data[i]);
}

Structure.ByReference案件。
tab_facet_fin_s tab = (tab_facet_fin_s)retFacet.readField("tab_facet_fin");
facet_fin_s s = (facet_fin_s)tab.readField("data");
facet_fin_s[] data = (facet_fin_s[])s.toArray(length);
for (int i = 0; i < data.length; i++)
{
   System.out.println(data[i].fin);
   System.out.println(data[i].facet);
}

现在我的意见:
  • readField策略可能是优化性能和避免无用复制的好方法。这可能是一个好技巧,但在这里不相关,因为我的结构只有我想读取的数据。如果我项目中的其他结构包含我不想阅读的数据,那么我将明确使用它。
  • 指针案例:不幸的是,JNAerator 确实会自动生成我的 dataStructure.ByReference而不是 Pointer .但是让我们想象一下我得到了这些 Pointer .然后我也可以非常快速地访问数据中的 int 值。如果我没记错的话,这种方式和调用 Pointer.getIntArray 完全一样.我在这里看到两个问题。首先,我完全失去了拥有 facet_fin_s 的好处。 Java 中的类。解析数据的方式还可以,但不是很方便。其次,如果我的结构 facet_fin_s拥有其他类型的成员(我试图绑定(bind)的库的某些结构就是这种情况),那么这个策略是无关紧要的。
  • Structure.ByReference 案例:这里的好处是我们将数据作为 facet_fin_s 的数组获取。 .这是代码可读性的一个好点。不幸的是,我们又回到了第一个有问题的地方,因为我们必须在这里使用这个该死的 Structure.toArray访问数据。此函数创建从 native 内存到 Java 内存的内存副本。对于大量数据,这个功能真的很慢。

  • 真的有什么方法可以非常快速地读取 native 内存数据并保持原始“架构”,而无需完全重写 Java 或 C 代码吗?
  • 继续使用代表 C 结构的 java 类
  • 尽可能避免用 Java 或 C 重写大量工具或类,以便我们只能使用 JNAerator
  • 对 native 内存的快速可读访问,或从 native 内存快速复制到 Java 内存

  • 我想我正面临着JNA的局限性......

    最佳答案

    您应该关闭结构内存的自动同步 ( Structure.setAutoSynch(false) )。然后您可以调用 Structure.readField(String) 仅根据需要访问感兴趣的领域。
    Structure.toArray()本身不会消耗那么多时间,但是将 native 内存同步到大量结构的 Java 字段最终会导致大量反射,这通常很慢。这将取决于所涉及的结构的数量以及每个结构中的字段数量(并且递归嵌套的结构引用会增加更多开销)。

    顺便说一句,您可以转换 Structure.toArray() 的结果直接到facet_fin_s[]所以你以后不必重复类型转换。

    如果您在许多结构中只有几个字段,并且需要访问所有这些字段,那么最好使用具有更好的 Java 到 native 传输性能的内存块表示(NIO 或原始数组)。无论使用什么平台,您都不想单独传输数千个字段。理想情况下,您希望将所有要传输的数据拉入单个缓冲区或数组并执行一次传输(Pointer.getIntArray() 可能适用于这种特殊情况)。

    编辑

    假设您的 data tab_facet_fin 内的字段是 Pointer 类型,然后您可以像这样提取数据:

    int[] buf = new int[LENGTH*2];
    tab_facet_fin.data.read(0, buf, 0, buf.length);
    

    相反,如果您映射 dataStructure.ByReference (即 struct* ),那么您需要执行以下操作:
    facet_fin_s s = (facet_fin_s)tab.readField("data");
    facet_fin_s[] data = (facet_fin_s[])s.toArray(LENGTH);
    

    请注意,您应该在要避免它的所有结构的 ctor 中设置 auto sync false ,以便在创建结构时自动发生。 Structure.toArray()来电Structure.autoRead()返回之前在所有数组元素上。

    编辑 2

    一般来说,JVM 对任何类型的本地访问都不友好。进行单个 native 函数调用的开销很大。 Structure.toArray() 的实际开销正在逐个读取每个字段,每次读取都会导致 JNI 交叉。最好的解决方案是尽可能少地进行 JNI 转换,因此这意味着传输数据,然后将其分类到其组成部分中。

    如果您将所有内容都放在一个缓冲区中,您仍然可以使用 JNA 计算的信息来访问它。你可以想象自己制作Memory由 native 内存支持的类,但经过优化以读取整个 native 内存块一次,然后覆盖所有 Pointer.getXXX访问 Java 端缓冲区而不是 native 内存的方法。这可能是 JNA 中的一个有用功能,并且可能是默认优化。缺点是你现在有两倍的内存使用量,所以它不一定总是最好的解决方案。

    注意:扩展 JNAerator 生成的接口(interface)以添加未配置为生成的映射是微不足道的。例如,如果它发出以下内容:
    interface MyLibrary extends Library {
        void myFunction(Pointer arg);
    }
    

    你可以像这样增加它:
    interface MyLibrary2 extends MyLibrary {
        void myFunction(MyStructure arg);
    }
    

    关于java - 在没有 toArray 的情况下提高 JNA 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17442430/

    相关文章:

    java - 重命名使用 Gradle 构建的项目

    C - 数组超出范围

    java - JNA:指向字符的指针**

    java - JAVA中调用将字符串写入Excel工作表的方法时出现线程 "main"java.lang.NullPointerException错误

    java - 是否可以从静态方法开始使用 Lombok @Builder?

    java - Libgdx场景2D : Add table in seperate class

    c++ - 我应该如何管理 C 或 C++ 开源项目中的依赖项?

    c - realloc() 泄漏内存

    java - 如何在 OSX 中使用 Java 获取 Activity 应用程序标题?

    java - JNA 返回字符*