Эффективность XSLT-преобразования - PullRequest
9 голосов
/ 22 октября 2008

Я инженер поддержки, и продукт нашей компании позволяет преобразовывать XSLT для настройки выходных данных.

Для этого я сделал xsl-преобразование. Он хорошо работает для исходных файлов типичного размера (несколько сотен тысяч), но иногда может возникнуть очень большой (10 миллионов) исходный файл. В таком случае вывод не генерируется, даже если я позволю ему перемолоть несколько дней.

Команда разработчиков SW проверила его и обнаружила, что для рассматриваемого преобразования и большого исходного файла действительно очень медленно (> дней), если наш продукт скомпилирован с использованием механизма преобразования в .Net 1.1, но если они его скомпилируют с .Net 2.0 это достаточно быстро (около 1-2 минут).

Долгосрочное решение, очевидно, состоит в том, чтобы ждать следующего выпуска.

В краткосрочной перспективе мне интересно следующее: 1) Достаточно ли гибок XSLT, чтобы существовали более эффективные и менее эффективные способы достижения того же результата? Например, возможно ли, что, как я структурировал xsl, движок преобразования должен многократно повторять начало исходного файла, занимая все больше и больше времени, когда следующий фрагмент результата становится все дальше и дальше от начала? (Шлемель Художник), или 2) Это больше зависит от того, как механизм преобразования интерпретирует xsl?

Если 2 имеет место, я не хочу тратить много времени, пытаясь улучшить xsl (я не большой гений xsl, мне было достаточно трудно добиться того, что я мало сделал ...) .

Спасибо!

Ответы [ 5 ]

5 голосов
/ 22 октября 2008

Я не знаком с реализациями .NET, но есть несколько вещей, которые вы можете сделать в целом, чтобы ускорить обработку больших документов:

  • Избегайте использования "//" в выражениях Xpath, если это не является абсолютно необходимым.
  • Если вам нужен только первый или единственный элемент, который соответствует выражению Xpath, используйте квалификатор «[1]», например, "// IFrame [1]". Многие процессоры реализуют оптимизации для этого.
  • Когда это возможно, при работе с огромным вводом XML посмотрите, можете ли вы разработать решение на основе потокового парсера (например, SAX) вместо парсера на основе DOM.
4 голосов
/ 23 октября 2008

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

С XSLT трудно создать нелинейную кривую производительности, если вы выполняете весь анализ с прямыми совпадениями шаблонов:

<xsl:template match="foo">
  <!--OUTPUT-->
  <xsl:apply-templates / >
  <!--OUTPUT-->
</xsl:template>

 <xsl:template match="bar">
  <!--OUTPUT-->
  <xsl:apply-templates / >
  <!--OUTPUT-->
</xsl:template>

Обратите особое внимание на то, где вы могли прибегнуть к <xsl:for-each> для разбора; совпадения с шаблонами практически всегда являются лучшим способом достижения того же результата.

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

<xsl:template match="*">
  <xsl:copy>                   <!--Copy node                   -->
    <xsl:copy-of select="@*"/> <!--Copy node attributes         -->
    <xsl:apply-templates />    <!--Process children             -->
  </xsl:copy>
</xsl:template>

Это будет сопоставлять и копировать каждый узел, по одному, в новый документ. Это не должно демонстрировать нелинейное увеличение времени обработки по сравнению с размером ввода (если это имеет место, то проблема не в вашем коде XSLT).

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

3 голосов
/ 11 ноября 2008

Чтобы определить, когда начинать новый раздел, я сделал это:

<xsl:if test="@TheFirstCol>preceding-sibling::*[1]/@TheFirstCol"

Может ли это быть причиной или повторения?

Определенно. Выбранный вами алгоритм O (N 2 ) и будет очень медленным при достаточном количестве братьев и сестер, независимо от языка реализации.

Вот эффективный алгоритм с использованием ключей:

Solution1:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="text"/>

 <xsl:key name="kC1Value" match="@c1" use="."/>

    <xsl:template match="/">
      <xsl:for-each select="*/x[generate-id(@c1) = generate-id(key('kC1Value',@c1)[1])]">

       <xsl:value-of select="concat('&#xA;',@c1)"/>

       <xsl:for-each select="key('kC1Value',@c1)">
         <xsl:value-of select="'&#xA;'"/>
         <xsl:for-each select="../@*[not(name()='c1')]">
           <xsl:value-of select="concat('   ', .)"/>
         </xsl:for-each>
       </xsl:for-each>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

К сожалению, XslTransform (.Net 1.1) имеет общеизвестно неэффективную реализацию функции generate-id().

Следующее может быть быстрее с XslTransform:

Solution2:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="text"/>

 <xsl:key name="kC1Value" match="@c1" use="."/>

    <xsl:template match="/">
      <xsl:for-each select="*/x[count(@c1 | key('kC1Value',@c1)[1]) = 1]">

       <xsl:value-of select="concat('&#xA;',@c1)"/>

       <xsl:for-each select="key('kC1Value',@c1)">
         <xsl:value-of select="'&#xA;'"/>
         <xsl:for-each select="../@*[not(name()='c1')]">
           <xsl:value-of select="concat('   ', .)"/>
         </xsl:for-each>
       </xsl:for-each>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

При применении к следующему небольшому документу XML:

<t>
 <x c1="1" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="1" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="1" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="1" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="2" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="2" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="2" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="2" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="4" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="4" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="4" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="4" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="7" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="7" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="7" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="7" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="8" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="8" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="8" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="8" c2="1" c3="1" c4="0" c5="0"/>
</t>

оба решения дали желаемый результат:

1
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
2
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
3
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
4
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
5
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
6
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
7
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
8
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0

Из приведенного выше небольшого XML-файла я сгенерировал XML-файл размером 10 МБ, скопировав каждый элемент 6250 раз (используя другое преобразование XSLT :)).

В xml-файле размером 10 МБ и в XslCompiledTransform (.Net 2.0 +) оба решения имели следующие времена преобразования:

Решение 1: 3,3 с.
Решение 2: 2,8 с.

С помощью XslTransform (.Net 1.1) Solution2 работал в течение 1622 секунд; это около 27 минут.

2 голосов
/ 23 октября 2008

Одна вещь, проверяющая мир, заключается в том, что ваш XSLT часто просматривает другие части документа XML, то есть вы находитесь в одном узле контекста и ищете значение в другой части документа или даже в другом документе. Если вы делаете это, это может сильно ухудшить производительность, и вам следует подумать об использовании xsl: key и функции клавиш для этого. Он говорит процессору реализовать индекс быстрого поиска для данных.

Однажды я создавал XSLT, который работал 8 часов (с множеством перекрестных ссылок), и переключение на использование клавиш дало ему огромный прирост скорости.

1 голос
/ 23 октября 2008

Обнаружив вашу проблему, я нашел в Майкрософт КБ по этому поводу. Вы можете видеть это здесь .

Они говорят, что XSLT-преобразование в .NET 1 имеет некоторые проблемы с производительностью и что они могут предложить быстрое решение.

Если вы хотите попытаться устранить проблему, существует профилировщик XSLT здесь .

В противном случае вы можете увидеть, какие ссылки приведены на веб-сайте Microsoft для оптимизации проблем с скоростью XSLT ( ссылка ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...