Улучшение производительности JNA без toArray

Я пытаюсь создать привязку библиотеки C к моему коду Java с помощью JNA, но у меня очень плохие результаты.

Вот заголовочный файл 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 );

Вот файл С

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, верны, но операция выполняется очень медленно. Я прикинул, что вызов "toArray" на retFacet.tab_facet_fin.data занимает 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 Pointer или 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 как Structure.ByReference, а не Pointer. Но давайте представим, что я получаю эти Pointer. Тогда я также могу очень быстро получить доступ к значениям int внутри данных. Если я не ошибаюсь, этот способ точно такой же, как вызов Pointer.getIntArray. Я вижу здесь 2 проблемы. Во-первых, я полностью теряю преимущество наличия facet_fin_sclass в Java. Способ парсинга данных нормальный, но не очень удобный. Во-вторых, если моя структура facet_fin_s содержит члены другого типа (а это имеет место для некоторых структур библиотеки, которую я пытаюсь связать), то эта стратегия неуместна.

  • Случай Structure.ByReference: здесь хорошо то, что мы получаем данные в виде массива facet_fin_s. Это хороший момент для читаемости кода. К сожалению, мы вернулись к первой проблеме, потому что нам нужно использовать этот проклятый Structure.toArray для доступа к данным. Эта функция создает копию памяти из собственной памяти в память Java. Для большого количества данных эта функция очень медленная.

Есть ли способ очень быстро прочитать данные из собственной памяти и сохранить исходную «архитектуру» без полного переписывания кода Java или C?

  • Продолжайте использовать классы Java, представляющие структуры C
  • Избегайте, насколько это возможно, переписывания множества инструментов или классов на Java или C, чтобы мы могли использовать только JNAerator.
  • Быстрый и удобочитаемый доступ к собственной памяти или быстрое копирование из собственной памяти в память Java

Я думаю, что сталкиваюсь с ограничениями JNA...


person 3DesTy3    schedule 03.07.2013    source источник
comment
Похоже на работу для JavaCPP. Дайте мне знать, если вам нужно знать что-то конкретное, и я отвечу ниже!   -  person Samuel Audet    schedule 03.07.2013
comment
Привет Самуэль. Несколько дней назад я уже потратил некоторое время на ваш проект JavaCPP. Проблема в том, что у меня есть большая библиотека C для связывания со многими функциями и структурами, и я не могу позволить себе время, чтобы написать все оболочки, запрошенные JavaCPP. Более того, библиотека C будет продолжать улучшаться. Поэтому я больше искал решение, которое могло бы сгенерировать привязку для меня (например, JNAerator). Если JavaCPP может предоставить такую ​​функциональность, дайте мне знать. Я дам ему попробовать.   -  person 3DesTy3    schedule 03.07.2013
comment
На самом деле, я работаю над этим прямо сейчас. Вскоре я опубликую предварительный код, который работает с большинством заголовочных файлов C. Хотя это займет некоторое время, чтобы заставить это работать хорошо для каждого случая, я думаю, что у него есть хорошие шансы на успех там, где другие потерпели неудачу. Подпишитесь на список рассылки, если хотите получать новости об этом :)   -  person Samuel Audet    schedule 07.07.2013
comment
Та-да, и вот результат: Предустановки JavaCPP . Все удобства JNAerator без потери производительности :)   -  person Samuel Audet    schedule 16.09.2013


Ответы (1)


Вы должны отключить автоматическую синхронизацию памяти структуры (Structure.setAutoSynch(false)). Затем вы можете вызвать Structure.readField(String) только по мере необходимости для доступа к интересующим полям.

Structure.toArray() сам по себе не занимает так много времени, но синхронизация собственной памяти с полями Java для большого количества структур в конечном итоге вызывает много размышлений, что обычно происходит медленно. Это будет зависеть от количества задействованных структур и количества полей в каждой (а рекурсивно вложенные ссылки на структуры добавляют больше накладных расходов).

Кстати, вы можете преобразовать результаты Structure.toArray() непосредственно в facet_fin_s[], чтобы вам не пришлось повторять приведения позже.

Если у вас есть только несколько полей во многих структурах и вам нужен доступ ко всем из них, вам будет лучше использовать блочное представление памяти (NIO или примитивный массив), которое имеет лучшую производительность передачи Java для собственной передачи. Вы действительно не хотите передавать тысячи полей по отдельности, независимо от того, какая платформа для этого. В идеале вы захотите собрать все данные для передачи в один буфер или массив и выполнить передачу один раз (в этом конкретном случае может подойти 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);

Обратите внимание, что вы должны установить автоматическую синхронизацию в значение false в ctor всех структур, где вы хотите избежать этого, чтобы это происходило автоматически при создании структуры. 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);
}
person technomage    schedule 03.07.2013
comment
Привет, техномаг. Я уже пробовал трюк с автосинхронизацией, но у меня не получилось. Смотрите мою правку в исходном посте - person 3DesTy3; 04.07.2013
comment
Что касается Pointer.getIntArray(), то я его тоже уже протестировал. Производительность отличная (именно то, что я хочу), но я хочу связать библиотеку (для которой у меня нет исходного кода), которая использует структуры, содержащие множество полей (примитивы и структуры). Я действительно мог бы извлечь только необходимые данные благодаря пользовательской функции в C, а затем передать их с помощью NIO или примитивного массива, но, поскольку эти данные являются топологическими и геометрическими данными, я хотел бы не испортить архитектуру этих структур. - person 3DesTy3; 04.07.2013
comment
Вам нужно прочитать поле tab_facet_fin, прежде чем вы сможете читать его поля. - person technomage; 04.07.2013
comment
Хорошо, я сделал это, но я все еще не понимаю, как я наконец смогу читать данные в виде массива. Кастинг не работает (см. мой Edit2). Я до сих пор не знаю, смогу ли я избежать toArray... - person 3DesTy3; 04.07.2013
comment
Привет, техномаг. Спасибо за вашу помощь. Я добавил свои мысли о ваших решениях в Edit 3 (см. Выше) - person 3DesTy3; 05.07.2013