2009-06-21 2 views
5

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

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

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

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

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

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

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

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

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

Привет Timo, просто глупый вопрос, какой профайлер инструмент вы используете? –

+0

@Castanho - Я использовал PurifyPlus от IBM Rational. –

ответ

4

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

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

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

+1

OP не указывает кодировку для вызова getBytes(), который в дополнение к по умолчанию к текущему стандарту Locale выполняет кучу дополнительной работы для фактического извлечения этого Locale. –

+0

Задание локали в вызове getBytes(), по-видимому, оказывает благотворное влияние на потребление памяти, по крайней мере, но, к сожалению, не привело к реальному улучшению среды выполнения. Следующим шагом было бы переписать функции с помощью CharsetEncoder и посмотреть, улучшит ли это их. –

1

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

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

+0

Кэширование действительно звучит как опрятная идея, но, к сожалению, функция преобразования называется миллионными временами даже при сравнительно небольшом наборе данных, и строки различны. Вероятно, будет некоторое дублирование, но, учитывая, что мы уже сталкиваемся с ограничениями памяти на 32-битных JVM, кеширование преобразованных строк, скорее всего, вызовет больше проблем, чем это решит. –

+0

Затем вам нужно узнать, ПОЧЕМУ конверсия медленная .... –

+0

Кажется, что это связано с двумя вещами: (a) количество данных (не так много, что я могу с этим поделать) и (b) charset/locale Я конвертирую. До сих пор кажется, что преобразование в UTF-8 заметно быстрее, что неудивительно, но, к сожалению, сторона C++ в настоящее время не поддерживает UTF-8. –

2

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

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

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

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