Производительность Java в реальном времени - PullRequest
12 голосов
/ 22 мая 2009

Я работаю с Java-проектом, который требует очень продвинутых манипуляций с изображениями. Фактически, я делаю большую часть манипуляций с использованием OpenCV, и я использую JNI, чтобы обернуть нужные мне функции OpenCV. Я очень доволен производительностью, которую дает OpenCV, люди, которые написали код OpenCV, заслуживают большого уважения за код. В резком контрасте с тем, что я испытываю с кодом, написанным разработчиками Java.

Я начал с оптимизма в отношении выбора языка программирования, моя первая рабочая итерация проекта работает нормально, но его производительность не приближается к реальной (получая около 1 кадра в 2 секунды). Я провел некоторые оптимизации Мой код и его очень помогли. Я смог увеличить частоту кадров примерно до 10-20 кадров в секунду, и это здорово, но я обнаружил, что для дальнейшей оптимизации мне нужно переписать код Java, чтобы сделать то же самое, но 10 -20x эффективнее.

Я потрясен тем, как разработчики Java уделяют очень мало внимания производительности, особенно при написании классов для классов, связанных с Media. Я скачал OpenJDK и изучаю функции, которые использую. Например, в классе Raster есть функция getPixels (...), которая получает пиксели изображения. Я ожидал, что эта функция будет высоко оптимизированной функцией в исходном коде с несколькими вызовами System.arrayCopy для дальнейшей оптимизации производительности. Вместо этого я обнаружил чрезвычайно «классный» код, в котором они вызывают 5-6 разных классов и 10-20 разных методов, просто чтобы выполнить то, что я могу сделать в одной строке:

for (int i =0; i < n; i++) {
  long p = rawFrame[i];
  p = (p << 32) >>> 32;
  byte red = (byte) ((p >> 16) & 0xff);
  byte green = (byte) ((p >> 8) & 0xff);
  byte blue = (byte) ((p) & 0xff);
  byte val = (byte)(0.212671f * red + 0.715160f * green + 0.072169f * blue);
  data[i] = val;
  grayFrameData[i] = (val & 0x80) + (val & (0x7f)); 
}

Приведенный выше код преобразует изображение в градации серого и получает данные пикселей с плавающей запятой примерно за 1-10 мс. Если бы я хотел сделать то же самое со встроенными функциями Java, преобразование в градации серого само по себе занимает 200-300 мс, а затем захват пикселей с плавающей точкой занимает около 50-100 мс. Это недопустимо для производительности в реальном времени. Обратите внимание, что для ускорения я интенсивно использую побитовые операторы, от которых Java-разработчики уклоняются.

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

Мой вопрос заключается в том, что на этом позднем этапе разработки (у меня уже есть первая итерация, а не вторая, выполняющая больше в режиме реального времени), следует ли мне прикусить пулю и переключиться на C / C ++, где Я могу настраивать вещи намного больше, или я должен придерживаться Java и надеяться, что все станет более дружественным в реальном времени, так что мне не придется переписывать уже реализованный Java-код для ускорения.

Я действительно начинаю испытывать отвращение к тому, насколько "классной" и медленной является Java. Количество классов там кажется чрезмерным.

Ответы [ 9 ]

15 голосов
/ 22 мая 2009

Я провел работу по компьютерному зрению с Java, и меня могут опровергнуть за это, но он идеально подходит для компьютерного зрения и в реальном времени, вам просто нужно знать, как его использовать.

Потенциальные оптимизации:

Если вам нужна помощь в оптимизации вашего кода, я был бы рад помочь - например, я могу сказать вам, что вы, вероятно, получите повышение производительности, если сделаете метод

`public static final int getGrayScale(final int pixelRGB){
    return (0.212671f * ((pixelRGB >> 16) & 0xff) + 0.715160f * ((pixelRGB >> 8) & 0xff) + 0.072169f * ((pixelRGB) & 0xff));
}`

и использовать это в цикле for {пикселов}. Используя вызов метода, JVM может значительно активнее оптимизировать эту операцию и, вероятно, также может оптимизировать цикл for.

Если у вас есть оперативная память для записи, вы можете создать статическую, окончательную таблицу поиска выходных байтов в градациях серого для всех возможных 24-битных цветов пикселей. Это будет ~ 16 МБ в ОЗУ, но тогда вам не нужно выполнять арифметику с плавающей запятой, только доступ к одному массиву. может быть быстрее, в зависимости от того, какую JVM вы используете, и может ли она оптимизировать проверку границ массива.

Места, где можно найти похожий, более быстрый код обработки изображений:

Я бы настоятельно рекомендовал вам взглянуть на код для приложения обработки изображений ImageJ (невозможно связать из-за задержки StackOverflow) и его библиотек, в частности ij.process.TypeConverter. Как и ваш код, он в значительной степени опирается на операции с прямым массивом с разбивкой по битам и минимумом создания дополнительного массива . Библиотеки Java2D (часть стандартного JRE) и библиотека Java Advanced Imaging (JAI) (не могут связываться благодаря задержке StackOverflow) предоставляют другие способы быстрой обработки изображения непосредственно на данных изображения без необходимости каждый раз выполнять свою собственную операцию. время. Для Java2D вам просто нужно быть осторожным, какие функции вы используете.

Почему библиотеки Java2D такие непрямые:

Большая часть «класса» обусловлена ​​поддержкой нескольких цветовых моделей и форматов хранения (т. Е. Изображения HSB, плавающие цветовые модели, индексированные цветовые модели). Направленность существует по причине, а иногда и фактически повышает производительность - класс BufferedImage (например) подключается непосредственно к графической памяти в последних виртуальных машинах, чтобы сделать некоторые операции НАМНОГО быстрее. Indirection позволяет маскировать это от пользователя большую часть времени.

6 голосов
/ 22 мая 2009

Мой вопрос заключается в том, что на этом позднем этапе разработки (у меня уже есть первая итерация, а я не работаю над второй, которая выполняет больше в реальном времени), следует ли мне прикусить пулю и переключиться на C / C ++, где Я могу настраивать вещи намного больше, или я должен придерживаться Java и надеяться, что все станет более дружественным в реальном времени, чтобы мне не пришлось переписывать уже реализованный Java-код для ускорения.

Вы спрашиваете, должен ли я

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

Могут быть и другие варианты ... но вариант 2 не выглядит реалистичным, вы не можете просто "надеяться", что код станет быстрее: p

Несколько замечаний:

  1. OpenJDK не имеет такой же производительности, как Sun JDK, вы пробовали Sun JDK?
  2. Если необходимые вам оптимизации производительности выполняются несколькими способами, то, возможно, стоит переписать их и придерживаться Java ...
3 голосов
/ 22 мая 2009

Мое предположение: это зависит от того, насколько важно манипулирование изображениями по сравнению с проектом в целом, и относительно того, какие преимущества приносит Java. Ясно, что вы можете написать быстрый код в Java (как вы продемонстрировали), если вам нужно. Однако, если 80% вашего проекта будет состоять из такой оптимизации, я бы определенно переосмыслил Java как выбор языка.

С другой стороны, если это составляет 20% приложения, а остальные 80% составляют пользовательские функциональные возможности, связанные с предоставлением этого преобразования, то, возможно, придется выполнить работу, чтобы выполнить манипуляцию, - это полезный компромисс с Вы должны иметь дело с собственным управлением памятью и иметь любые другие API, которые Java предоставляет вам для взаимодействия с пользователем (Web, Swing, SWT, что вы используете).

Java не известна своими возможностями в реальном времени из-за сборщика мусора. Это может вас и укусить, так что будьте осторожны.

1 голос
/ 23 июня 2012

Не ясно, что вы действительно спрашиваете о реальном времени. Есть разница между реальным и очень быстрым. Для очень быстрого, достаточно рассмотреть поведение среднего случая. Пропускная способность - главная проблема. В режиме реального времени означает быть в состоянии завершить какую-то задачу в течение фиксированного периода времени каждый раз. Или, конечно, есть приложения, которые нуждаются в обоих.

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

Компиляция - это еще один момент, когда Java в реальном времени отличается от традиционных реализаций Java. Технология Java в реальном времени имеет тенденцию использовать статическую компиляцию или Ahead-of-Time (AoT) компиляцию вместо компиляции JIT. JiT может подойти для вашего приложения, так как вы можете выдержать время «прогрева», требуемое обычной виртуальной машиной для компиляции наиболее используемых классов. Если это так, то у вас, вероятно, нет требований в реальном времени, только пропускная способность.

Если вы заинтересованы в том, чтобы декодирование кадров не прерывалось сборкой мусора, то имеет смысл использовать реализацию Java в реальном времени и, возможно, компиляцию AoT. Спецификация реального времени для Java (RTSJ) также предоставляет другую поддержку для встроенного программирования в реальном времени, такую ​​как RelatimeThread, AsyncEventHandler и RawMemoryAccess.

Конечно, для получения хорошей производительности, в режиме реального времени или очень быстро, требуется внимание к деталям. Чрезмерное использование временного объекта не помогает. Распределение всегда влечет за собой дополнительные расходы, поэтому должно быть сведено к минимуму. Это серьезная проблема для функциональных языков, которые не позволяют изменять состояние объекта. Однако следует позаботиться о том, чтобы понять критические пути написанного кода, чтобы избежать ненужных оптимизаций. Профилирование важно для понимания того, где лучше всего оптимизировать.

1 голос
/ 22 мая 2009

Я не знаю, какое повышение производительности вы получите, но если у вас длительный процесс, выполняющий повторяющиеся действия, вы должны попытаться запустить ВМ Server Hotspot, используя java -server. Он работает намного лучше , чем клиентская виртуальная машина, установленная по умолчанию в Windows и оптимизированная для быстрого запуска.

0 голосов
/ 22 мая 2009

Что мешает вам написать оптимизированную версию методов, которые вы хотите использовать вместо использования встроенных методов? Если это невозможно, почему бы не написать свои объекты на более родном языке и импортировать их в существующее приложение?

0 голосов
/ 22 мая 2009

Насколько я понимаю, в самых последних версиях Java (или, может быть, это была JavaFX) есть методы, которые позволяют вам получить доступ к расширенным функциям видеооборудования вашей системы. Мне жаль, что я настолько общий, я думаю, что слышал об этом на Java Posse, и, поскольку я застрял в Java 1.3 land, у меня никогда не было возможности это проверить - но я помню, что слышал что-то подобное

Вот что-то об этом: Но похоже, что это будет только в Java 7: (

Также похоже, что сначала он будет поддерживать только воспроизведение потока и элементарную манипуляцию потоком, но, возможно, подход "Ожидание и улучшение Java" может действительно сработать.

0 голосов
/ 22 мая 2009

В дополнение к тому, что сказали другие, вы можете внести свой вклад в оптимизацию JDK. Если вы сможете обеспечить мощную оптимизацию, которая не жертвует универсальностью или удобочитаемостью, я ожидаю, что вы сможете включить свой патч в следующий выпуск JDK.

Таким образом, вам не нужно надеяться , что JDK может стать лучше. Вы можете помочь сделать это.

0 голосов
/ 22 мая 2009

Преждевременная оптимизация - корень всего зла.

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

Основной смысл эталонной реализации - сделать понятный, поддерживаемый код - так и должно быть. Я думаю, что всегда ожидалось, что в случае необходимости поставщики будут анализировать эту понятную версию и повторно реализовывать детали для скорости.

...