Подобрете представянето на 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 );

Ето 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, са правилни, но операцията е наистина бавна. Реших, че извикването на "toArray" на retFacet.tab_facet_fin.data отнема 38 секунди!!

Мисля, че JNA прекарват твърде много време, за да синхронизират структурите на Java с родната структура и да копират данните.

Опитах байтовите масиви и 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 Presets . Цялото удобство на 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
Здравей technomage. Вече опитах трика за автоматично синхронизиране, но не можах да го накарам да работи. Вижте моята редакция на оригиналния пост - 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
Здравей Technomage. Благодаря за помощта досега. Добавих мислите си относно вашите решения в Edit 3 (вижте по-горе) - person 3DesTy3; 05.07.2013