在没有toArray的情况下改善JNA性能

时间:2013-07-03 07:46:15

标签: java c jna

我正在尝试使用JNA创建C库与Java代码的绑定,但是我的性能非常差。

这是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结构与本机结构同步并复制数据。

我尝试了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策略。有两种获取数据的方式,具体取决于dataPointer还是Structure.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策略可能是优化性能和避免无用副本的好方法。这可能是一个很好的技巧,但在这里不相关,因为我的结构只有我想要阅读的数据。如果我的项目中的其他结构包含我不想阅读的数据,那么我将最终使用它。

  • Pointer案例:不幸的是,JNAerator会自动生成data Structure.ByReference而非Pointer。但让我们想象一下,我得到了这些Pointer。然后我也可以非常快速地访问数据内部的int值。如果我没错,这种方式与调用Pointer.getIntArray完全相同。我在这看到2个问题。首先,我完全放弃了在Java中使用facet_fin_s类的好处。解析数据的方式还可以,但不是很方便。其次,如果我的结构facet_fin_s拥有其他类型的成员(这是我试图绑定的库的某些结构的情况),那么这个策略是无关紧要的。

  • Structure.ByReference案例:这里的好处是我们将数据作为facet_fin_s的数组获取。这是代码可读性的一个好点。不幸的是,我们回到了第一个有问题的因为我们在这里使用这个该死的Structure.toArray来访问数据。此函数创建从Native内存到Java内存的内存副本。对于大量数据,此功能非常慢。

有没有办法以非常快的方式读取本机内存数据并保留原始的“架构”,而不完全重写Java或C代码?

  • 继续使用代表C结构的java类
  • 尽可能避免在Java或C中重写大量工具或类,以便我们只能使用JNAerator
  • 对本机内存的快速可读访问,或从本机内存到Java内存的快速复制

我认为我正面临着JNA的局限......

1 个答案:

答案 0 :(得分:2)

您应该关闭结构内存的自动同步(Structure.setAutoSynch(false))。然后,您只能根据需要调用Structure.readField(String)来访问感兴趣的字段。

Structure.toArray()本身并不消耗那么多时间,但是将本机内存与Java字段同步以用于大量结构最终导致大量反射,这通常很慢。这将取决于所涉及的结构数量和每个结构中的字段数量(以及递归嵌套结构引用会增加更多开销)。

顺便说一句,您可以将Structure.toArray()的结果直接投射到facet_fin_s[],这样您就不必在以后重复投射。

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

修改

假设data中的tab_facet_fin字段属于Pointer类型,那么您可以像这样提取数据:

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

如果您将data映射为Structure.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对任何类型的本机访问都不友好;进行单个本机函数调用会产生很大的开销。 Structure.toArray()的实际开销是逐个读取每个字段,每次读取都会导致JNI交叉。最好的解决方案是尽可能少地进行JNI转换,这意味着传输数据然后然后将其分类到其组成部分。

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

注意:扩展JNAerator生成的接口以添加未配置为生成的映射是很简单的。例如,如果它发出以下内容:

interface MyLibrary extends Library {
    void myFunction(Pointer arg);
}

你可以像这样扩充它:

interface MyLibrary2 extends MyLibrary {
    void myFunction(MyStructure arg);
}