Любое предложение о том, как улучшить производительность преобразования Java String в byte []? - PullRequest
5 голосов
/ 21 июня 2009

Я унаследовал фрагмент кода, который интенсивно использует преобразования String -> byte [] и наоборот для некоторого собственного кода сериализации. По сути, объекты Java знают, как преобразовать свои составные части в строки, которые затем преобразуются в байт []. Указанный байтовый массив затем передается через JNI в код C ++, который восстанавливает byte [] в C ++ std :: strings и использует их для начальной загрузки объектов C ++, которые отражают объекты Java. Это немного больше, но это высокоуровневое представление о том, как работает этот фрагмент кода; Связь работает так в обоих направлениях, так что переход C ++ -> Java является зеркальным отражением перехода Java -> C ++, о котором я упоминал выше.

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

Основная схема кода выглядит следующим образом:

public void convertToByteArray(String convert_me, ByteArrayOutputStream stream)
{
  stream.write(convert_me.getBytes());
}

Функция немного больше, но не намного. Вышеупомянутая функция вызывается один раз для каждого объекта String / Stringified, и после того, как все составляющие записаны в ByteArrayOutputStream, ByteArrayOutputStream преобразуется в byte []. Если разбить вышесказанное на более удобную для профилировщика версию путем извлечения вызова convert_me.getBytes(), то видно, что более 90% времени в этой функции тратится на вызов getBytes ().

Есть ли способ улучшить производительность вызова getBytes () или есть другой, потенциально более быстрый способ добиться того же преобразования?

Количество конвертируемых объектов довольно велико. На прогонах профилирования, которые используют только небольшое подмножество производственных данных, я вижу что-то вроде 10 миллионов плюс обращений к вышеуказанной функции преобразования.

В связи с тем, что мы очень близки к тому, чтобы выпустить проект в производство, есть несколько обходных путей, которые на данный момент невозможны:

  • Перепишите интерфейс сериализации, чтобы просто передавать объекты String через уровень JNI. Это очевидный (для меня) способ улучшения ситуации, но он потребует серьезного реинжиниринга уровня сериализации. Учитывая тот факт, что мы собираемся в UAT в начале этой недели, уже слишком поздно для такого рода сложных изменений. Это моя главная задача для следующего релиза, так что это будет сделано; Однако до тех пор мне нужен обходной путь, но пока код работает, используется годами и имеет большую часть изломков. Ну, кроме спектакля.
  • Изменение JVM (в настоящее время 1.5) также не вариант. К сожалению, это JVM по умолчанию, которая установлена ​​на клиентских компьютерах, и обновление до версии 1.6 (которая может или не может быть быстрее в этом случае), к сожалению, невозможно. Любой, кто работал в крупных организациях, вероятно, понимает, почему ...
  • В дополнение к этому, мы уже сталкиваемся с ограничениями памяти, поэтому попытка кэшировать хотя бы более крупные строки и представление их байтового массива, будучи потенциально элегантным решением, может вызвать больше проблем, чем решит

Ответы [ 4 ]

4 голосов
/ 21 июня 2009

Я предполагаю, что часть проблемы может заключаться в том, что строка Java имеет формат UTF-16 - то есть два байта на символ; поэтому getBytes() выполняет всю работу по преобразованию каждого элемента UTF-16 в один или два байта, в зависимости от вашего текущего набора символов.

Вы пытались использовать CharsetEncoder - это даст вам больший контроль над кодировкой String и позволит вам пропустить некоторые издержки в реализации getBytes по умолчанию.

В качестве альтернативы, вы пытались явно указать кодировку для getBytes и использовать US-ASCII в качестве набора символов?

2 голосов
/ 21 июня 2009

вижу несколько вариантов:

Если у вас есть строки Latin-1, вы можете просто разделить старший байт символов в строке (Charset делает это тоже, я думаю) Вы также можете разделить работу между несколькими ядрами, если у вас есть больше (у структуры fork-join был backport до 1.5 один раз) Вы также можете встроить данные в построитель строк и преобразовать их в байтовый массив только один раз в конце. Посмотрите, как вы используете ГХ / память. Слишком большое использование памяти может замедлить ваши алгоритмы из-за частых прерываний GC
1 голос
/ 09 апреля 2017

Проблема в том, что все методы в Java, даже сегодня, выделяют память при производстве UTF-8. Чтобы получить кодировщик, вам нужно написать собственный код и повторно использовать буфер byte []. Colfer может сгенерировать код или просто скопировать его реализацию.

https://github.com/pascaldekloe/colfer/blob/4c6f022c5183c0aebb8bc73e8137f976d31b1083/java/gen/O.java#L414

1 голос
/ 21 июня 2009

Если это те же строки, которые вы конвертируете все время, вы можете кэшировать результат в WeakHashMap.

Кроме того, взгляните на метод getBytes () (источник доступен, если вы установите SDK), чтобы увидеть, что именно он делает.

...