0

我正在尝试使用 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策略。有两种获取数据的方法,取决于data是 aPointer还是 a 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策略可能是优化性能和避免无用复制的好方法。这可能是一个好技巧,但在这里不相关,因为我的结构只有我想读取的数据。如果我项目中的其他结构包含我不想阅读的数据,那么我将明确使用它。

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

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

真的有什么方法可以非常快速地读取本机内存数据并保持原始“架构”,而无需完全重写 Java 或 C 代码吗?

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

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

4

1 回答 1

2

You should turn off auto-synch of structure memory (Structure.setAutoSynch(false)). Then you can call Structure.readField(String) only as needed to access the fields of interest.

Structure.toArray() doesn't by itself consume that much time, but synching native memory to Java fields for a large number of structures ends up causing a lot of reflection, which is generally slow. This will depend on the number of structures involved and the number of fields in each (and recursively nested structure references add more overhead).

BTW, you can cast the results of Structure.toArray() directly to facet_fin_s[] so you don't have to repeat the casts later.

If you've only got a few fields in the many structs, and need to access all of them, you'll be better off with a block representation of memory (NIO or primitive array) that has better Java to native transfer performance. You really don't want to be transferring thousands of fields individually no matter what the platform for doing so. Ideally you'll want to pull all of the data to be transferred into a single buffer or array and perform the transfer once (Pointer.getIntArray() might serve for this particular case).

EDIT

Assuming your data field within tab_facet_fin is of type Pointer, then you can extract your data like this:

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

If instead you map data as Structure.ByReference (i.e. struct*), then you'd need to do the following:

facet_fin_s s = (facet_fin_s)tab.readField("data");
facet_fin_s[] data = (facet_fin_s[])s.toArray(LENGTH);

Note that you should set auto sync false in the ctor of all structures where you want to avoid it so that it happens automatically when the structure is created. Structure.toArray() calls Structure.autoRead() on all array elements before returning.

EDIT 2

In general, the JVM is not kind towards native accesses of any sort; there is a large overhead for making a single native function call. The real overhead of Structure.toArray() is reading each field one by one, each read of which results in a JNI crossing. The best solution is to make as few JNI transitions as possible, so this means transferring the data and then sorting it out into its component parts.

If you pull everything over in a single buffer, you can still use the information calculated by JNA to access it. You could conceivably make your own Memory class backed by native memory, but optimized to read the entire native memory chunk once and then override all the Pointer.getXXX methods to access a Java-side buffer instead of native memory. This might be a useful feature in JNA, and could conceivably be the default optimization. The drawback would be that you now have twice the memory usage, so it's not necessarily always the best solution.

NOTE: it is trivial to extend the interfaces generated by JNAerator to add mappings that it wasn't configured to generate. For instance, if it emits the following:

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

You can augment it like this:

interface MyLibrary2 extends MyLibrary {
    void myFunction(MyStructure arg);
}
于 2013-07-03T11:56:17.227 回答