Отключение интернирования строк в Jaxb2 ускоряет демаршаллинг из потока Fastinfoset. Почему так сложно отключить?
Я измерил временные затраты на демонтаж XML для объектов, использующих Jaxb2, с использованием большой (1,7 МБ) полезной нагрузки XML с несколько длинными (48 символов) именами тегов. Через JProfiler, работающий в режиме выборки, я заметил, что работа по интернированию строк занимает большую часть затраченного времени.
Я провел некоторое исследование и обнаружил, что Jaxb может работать в режиме, в котором он не интернирует строки. Моя теория состояла в том, что в некоторых случаях отсутствие интернирующих строк во время демаршаллинга может улучшить производительность за счет использования большего количества динамической памяти из-за того, что не требуется хэшировать каждую строку имени тега в процессе интернирования.
Метод, который я использовал для подавления интернирующего поведения Jaxb, заключался в том, чтобы установить в моем Fastinfoset "StAXDocumentParser" (который реализует XMLStreamReader) свойства "org.codehaus.stax2.internNames" и "org.codehaus.stax2.internNsUris". Мне не на 100% ясно, почему вы должны установить для них значение "true", чтобы Jaxb не интернировал строки, но именно так оно и работает.
Эти тесты на основе JUnit- это то, что я использовал для вывода о том, что отключение поведения интернирования строк в Jaxb сильно влияет на производительность:
https://github.com/gjd6640/fastinfoset-performance-evaluation
Итак, мой вопрос состоит из нескольких частей:
1) Я неправильно понимаю что-то важное и не должен пытаться отключить поведение Jaxb для интернирования строк?
2) Есть ли лучший способ направить Jaxb, чтобы не интернировать строки? Класс "StAXManager" не позволяет вам устанавливать эти Woodstox-ориентированные свойства. В этом тесте я расширил StAXManager, как показано ниже, для решения проблемы. Это хак, который я предпочел бы не использовать в производстве. Я подозреваю, что идея заключается в том, что когда Jaxb выполняет демаршаллинг из потока Woodstox, он проверяет, выполняет ли Woodstox уже интернирование, и когда "да" Jaxb реагирует отключением этого шага процесса. Я обманываю, используя эту логику в библиотеке Jaxb, поэтому хотел бы найти лучший способ сделать это.
package com.sun.xml.fastinfoset.stax;
public class JaxbStringInternSuppressionStaxManager extends StAXManager {
public JaxbStringInternSuppressionStaxManager() {
// Add to the allowable list of feature names so that the user may set these "StAXInputFactory" properties
super.features.put("org.codehaus.stax2.internNames", null);
super.features.put("org.codehaus.stax2.internNsUris", null);
}
}
Обновить:
Как обычно, "хорошо поставленный вопрос - наполовину ответ". Я только заметил, что при составлении этого вопроса "com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector" проверяет, является ли класс "com.sun.xml.Internal.fastinfoset.stax.StAXDocumentParser" присваивается из используемого вами XMLStreamReader и в этом случае не включает интернирование строк. В моем случае мой потоковый объект является "com.sun.xml.fastinfoset.stax.StAXDocumentParser", поэтому интернирование не отключается. Теперь возникает вопрос: "Почему он делает это только для внутреннего вкуса библиотеки Fastinfoset?" Может быть, я найду ответ, внимательно прочитав этот пост.
Кроме того, если есть лучший форум для такого рода вопросов, такой как активная группа пользователей разработчиков, пожалуйста, поделитесь этой информацией, и я посмотрю о том, как связать их с этим постом, чтобы правильные люди увидели этот вопрос.
2 ответа
Я бы не стал доверять профилировщику или тесту, не измеряя реальный вариант использования с интернированием и без него, так что будьте немного скептиками. Тем не менее, есть некоторые проблемы со стажером. В частности, он использует пул фиксированного размера, поэтому, когда пул заполнен, потенциальная постоянная производительность для поиска в хэшах снижается до поиска в связанных списках. См. http://java-performance.info/string-intern-in-java-6-7-8/ для более подробного обсуждения.
Короче говоря, вы можете попытаться изменить размер пула с -XX:StringTableSize=n
(где n в идеале должно быть простым) и посмотрим, что произойдет.
использование -XX:+PrintStringTableStatistics
чтобы увидеть, как пул использовался при завершении программы, и попробуйте разные размеры.
РЕДАКТИРОВАТЬ: это была попытка ответить "есть ли лучший путь" (т.е. сделать стажера быстрее). Я оставлю другой вопрос кому-то более квалифицированному.
Вариант решения 1. Простой подход, при котором все приложение переключается на другую реализацию jaxb.
Вставьте jaxb-impl, чтобы использовать версию Jaxb, которая работает лучше с этой библиотекой Fastinfoset:
<!-- Both of these libs must be here in order to get performant behavior out of Jaxb by default.
-->
<dependency>
<groupId>com.sun.xml.fastinfoset</groupId>
<artifactId>FastInfoset</artifactId>
<version>1.2.13</version>
<scope>compile</scope>
</dependency>
<dependency> <!-- This artifactId also exists under javax.xml.bind but it appears that nobody uses that one... -->
<groupId>javax.xml</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.1</version>
<scope>runtime</scope>
</dependency>
<!-- End: Both of these libs... -->
Это будет иметь побочный эффект обновления версии jaxb, используемой остальной частью вашего кода. В некоторых ситуациях это может быть нежелательно. Например, если вы создаете разделяемую библиотеку, которую необходимо использовать в различных приложениях, было бы грубо пойти и изменить эту функцию, когда они загружают ваш разделяемый компонент.
Вариант решения 2. Используйте реализацию jaxb JVM и хак производительности, чтобы заставить его поверить, что строки уже интернированы (более сложный для реализации)
- Используйте "maven-shade-plugin", чтобы закрасить и упаковать классы библиотеки Fastinfoset. Результат должен быть логическим компонентом maven. Это необязательно и предназначено для того, чтобы у людей, использующих ваш компонент кодека Fastinfoset, не возникало коллизий пути к классам из-за транзитивных зависимостей, извлекаемых вашей библиотекой кодеков.
- Создайте библиотеку my-fastinfoset-codec, которая предоставляет простой API для кодирования и декодирования полезных нагрузок Fastinfoset (рассмотрите возможность использования InputStreams и OutputStreams для аргументов и XMLStreamReader для возвращаемого типа декодера). Добавьте зависимость от вашей переупакованной библиотеки Fastinfoset. Обратите внимание, что если вы используете Eclipse, он плохо работает с затененными библиотеками, когда "разрешение рабочего пространства" в m2e включено, поэтому отключите его для своего проекта кодека.
- Добавьте в my-fastinfoset-codec класс, который расширяет пакет StAXManager из перепакованной библиотеки Fastinfoset. Этот класс должен облегчать установку свойств, которые сообщают jaxb, что предоставленный ему XMLStreamReader уже интернировал строки имен NS и тегов. Пример ниже:
пакет myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax; import myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax.StAXManager; открытый класс JaxbStringInternSuppressionStaxManager extends StAXManager { public JaxbStringInternSuppressionStaxManager() { // Добавить в допустимый список имен объектов, чтобы пользователь мог установить эти свойства "StAXInputFactory" super.features.put("org.codehaus.stax2.internNames", null); super.features.put("org.codehaus.stax2.internNsUris", null); } /** * Это оптимизация. Библиотеки FastInfoset уже содержат строки и реализацию jaxb JVM по умолчанию * излишне повторяет эту работу. Это верно по крайней мере для 64-битной версии jdk1.8.0_121. * * Способ работы этого обходного пути заключается в использовании оптимизации Jaxb для анализатора Woodstox. Когда мы установим * эти свойства говорят jaxb, что Woodstox уже интернировал строки, что заставляет его отключать * интернирование строк. * * Мы исследовали более чистый вариант добавления артефакта Maven "javax.xml:jaxb-impl" в качестве зависимости вместо использования * Jax-библиотека JVM. Эта внешняя библиотека jaxb при использовании с библиотекой FastInfoset работает значительно лучше * чем JVM, но не на 100% быстрее, чем JVM с отключенным интернированием. Основная причина, по которой мы прекратили исследовать это решение * в том, что когда вы перепаковываете (через maven-shade-plugin) библиотеки jaxb, они больше не работают с нашей стандартной привязкой jaxb * компоненты maven из-за операторов типа "if ( instanceof my_repackaging_project.shaded.XMLElement)" * используется в процессе отображения данных. */ public JaxbStringInternSuppressionStaxManager enableTrickToStopJaxbFromInterningStrings() { super.setProperty("org.codehaus.stax2.internNames", true); super.setProperty("org.codehaus.stax2.internNsUris", true); верни это; } }
Вариант решения 3. Достаточно людей, имеющих контракт на поддержку JVM с Oracle, собирают билеты, требующие какой-либо внешней поддержки fastinfoset.
Я ожидаю, что для Oracle будет довольно просто научить Jax-реализацию, предоставляемую JVM, определять из заданного XMLStreamReader, что эта реализация Fastinfoset сконфигурирована для интернирования строк.
Возможность решения, которая не удалась: упакуйте две банки из решения 1 выше
Можно использовать "maven-shade-plugin" или аналогичный для создания новых jar-файлов с именами пакетов с префиксом. Это сработало с этими библиотеками после некоторой возни. Однако в результате я пришел к тому, что переупакованные библиотеки jaxb теперь хотели, чтобы объекты OXM, созданные jaxb-RI, имели аннотации от нового заштрихованного имени пакета. Мои были построены стандартным способом, поэтому мое переупакованное решение не отображало данные на мои объекты. Я не хочу диктовать, что наши библиотеки связывания OXM используют переупакованную библиотеку jaxb, и мне не понравился этот подход, чтобы более тщательно изучить способы переупаковки, чтобы не изменять пакет, используемый для этих аннотаций.
Вариант решения, который я не исследовал:
Используйте классы fastinfoset JVM, имеющие ".internal". в их названиях пакетов. Они, вероятно, будут работать хорошо с реализацией jaxb, которая поставляется с JVM, но я отказываюсь подвергать "будущее меня" расходам на поддержку, связанным с использованием внутреннего apis.