Настройка производительности JNA Fortran - PullRequest
1 голос
/ 27 апреля 2020

Я обертываю нативный код (в основном Fortran 77) с использованием JNA. Вывод (т.е. результаты) нативной функции состоит из набора вложенных (пользовательских) типов / структур, которые я сопоставляю с соответствующими Structure в JNA. Эти Structures в основном состоят из массива других Structures (поэтому структура A содержит массив структуры B, структура B содержит массив структуры C et c).

Используя тот же бенчмаркинг (в основном, регистрируя разницу во времени), я обнаружил, что большую часть времени тратится не на собственный код, а на отображение JNA. Вызов подпрограммы Fortran занимает около 50 мс, но общее время составляет 250 мс.

Я обнаружил, что

  • .setAutoWrite(false) на нашем Structure уменьшает накладные расходы в ~ 2 раза (общее время выполнения почти наполовину)
  • Сохранение ( статически распределенные) массивы, насколько это возможно, помогают поддерживать низкие издержки JNA
  • Изменение DOUBLE PRECISION (double) на REAL (float), по-видимому, не имеет никакого значения

Есть ли еще какие-нибудь приемы для оптимизации производительности JNA в нашем случае? Я знаю, что могу сгладить свои структуры до одномерного массива примитивов и использовать прямое отображение, но я стараюсь этого избегать (потому что кодировать / декодировать эти структуры будет непросто)

1 Ответ

1 голос
/ 02 мая 2020

Как отмечено в JNA FAQ , прямое сопоставление будет вашим лучшим увеличением производительности, но вы исключили это в качестве опции. Он также отмечает, что накладные расходы на вызовы для каждого собственного вызова - это еще одно снижение производительности, которое вы частично исправили, изменив setAutoWrite().

. Вы также упоминали о сведении ваших структур к массиву примитивов, но отклонили это. из-за сложности кодирования / декодирования. Однако движение в этом направлении, вероятно, является следующим лучшим выбором, и, возможно, самая большая проблема с производительностью, с которой вы сталкиваетесь в настоящее время, - это сочетание доступа JNA Structure с использованием рефлексии и собственного чтения. Oracle примечания :

Поскольку отражение включает типы, которые разрешаются динамически, некоторые Java оптимизации виртуальной машины не могут быть выполнены. Следовательно, отражающие операции имеют более низкую производительность, чем их неотражающие аналоги, и их следует избегать в разделах кода, которые часто вызываются в чувствительных к производительности приложениях.

Поскольку вы здесь задаете вопрос, связанный с производительностью вопрос и используя JNA Structures, я могу только предположить, что вы пишете «чувствительное к производительности приложение». Внутренне, Структура делает это:

for (StructField structField : fields().values()) {
    readField(structField);
}

, который выполняет по одному Нативному чтению для каждого поля, затем следует, что заканчивается использованием отражения под капотом.

setFieldValue(structField.field, result, true);

Мораль история в том, что обычно в Structures, как правило, каждое поле включает в себя нативное чтение + рефлексивная запись или рефлексивное чтение + нативная запись.

Первый шаг, который вы можете сделать, не внося никаких других изменений, - setAutoSynch(false) на строении. (Вы уже сделали половину этого с версией «запись»; она выполняет как чтение, так и запись.) Из документов:

Для очень больших или сложных структур, где вам нужен только доступ к Небольшое количество полей позволяет значительно повысить производительность, избегая автоматических операций чтения и записи c. Если автоматическое чтение и -write отключены, вы должны убедиться, что поля, представляющие интерес Java, синхронизированы до и после вызовов собственных функций через readField (String) и writeField (String, Object). Как правило, это наиболее эффективно, когда собственный вызов заполняет большую структуру, и вам нужно всего лишь несколько полей. После нативного вызова вы можете вызвать readField (String) только для интересующих вас полей.

На самом деле go, выпрямление, возможно, поможет еще немного избавиться от любого отражения накладные расходы. Хитрость в том, чтобы упростить преобразование смещения.

Некоторые указания на go, соотношение сложности и производительности:

  • Для записи в собственную память, выделите и очистите буфер байтов (mem = new Memory(size); mem.clear(); или просто new byte[size]) и запишите указанные поля c в смещение байта, которое вы определяете, используя значение из Structure.fieldOffset(name). При этом используется отражение, но вы можете сделать это один раз для каждой структуры и сохранить карту имени для смещения для последующего использования.
  • Для чтения из собственной памяти сделайте все ваши собственные вызовы чтения, используя плоский буфер, чтобы уменьшить нативные накладные расходы на одно чтение / запись. Вы можете преобразовать этот буфер в Структуру, когда вы читаете его (вызывая отражение для каждого поля один раз) или читаете указанные c смещения байтов в соответствии с вышеуказанной стратегией.
...