О производительности при работе с временными деревьями

Содержание

Задача.
Разобрать по косточкам теорию и практику использования временных деревьев в XSLT.

Думаю, читателю, как и мне, интересно, насколько тяжело трансформатору создавать временные деревья и работать с ними. Для того чтобы в этом разобраться, было сделано несколько тестов. «Подопытными кроликами» выступали четыре рассмотренных в прошлой части трансформатора:

  • libxslt 1.1.26;
  • Xalan-Java 2.7.1;
  • Saxon-Java 9.1.0.8;
  • MSXML 4.0.

Входящие данные

  • Каждый тест имел какую-либо цель. Эта цель достигалась двумя способами — без использования временных деревьев и с их использованием.
  • Числовым результатом теста являлось время его выполнения. Использовались показания самих трансформаторов, которые после преобразования выводили потраченное время.
  • Каждый тест прогонялся по 5 раз, итоговым результатом являлось среднее из этих 5 прогонов. Чем меньше результат (время трансформации), тем лучше.
  • На вход трансформаторам подавался XML объемом 50 КБ, состоящий примерно из 500 элементов.
  • Транзисторная аппаратура — MacBook Pro # Intel Core i7 2.66 GHz, 8 GB RAM.
  • MSXML работала в виртуальной Windows внутри VMware Fusion.
  • Все трансформации запускались из командной строки.

Тест № 1 — создание временного дерева из входящего XML

В первом, слегка синтетическом, тесте входящий XML копируется без каких-либо изменений.

Сначала просто:

<xsl:template match="/">
    <xsl:copy-of select="node()" />
</xsl:template>

А затем сложно — с превращением во временное дерево:

<xsl:template match="/">
    <xsl:variable name="input">
        <doc>
            <xsl:copy-of select="node()" />
        </doc>
    </xsl:variable>

    <xsl:copy-of select="exsl:node-set($input)/doc/node()" />
</xsl:template>

И вот графики. Каждому трансформатору на графике соответствуют два столбика: левый — замер без использования временного дерева, правый — с использованием. Каждый столбик (каждый замер) состоит из 3 частей: нижняя — парсинг XML, средняя — парсинг XSL, верхняя — трансформация. Исключением из этого правила является Xalan — он, в отличие от своих коллег, предоставляет в логе только полное время преобразования, поэтому его столбик состоит лишь из одной части. Для нас самой интересной частью является трансформация, поэтому числами на графике помечена только она.

Результаты теста № 1
Результаты теста № 1

Может показаться, что Java-трансформаторы сливают по полной. Однако не будем забывать, что трансформации запускались из командной строки, поэтому значительное время тратилось на старт Java-машины и подтягивание необходимых jar-ов. И самое главное: наша задача заключается не в сравнении трансформаторов, а в замере влияния временных деревьев на каждый из них. И влияние это налицо — превращение входящего XML на 500 элементов во временное дерево заняло у всех трансформаторов больше времени, чем простое копирование этого XML. Пара дополнительных тестов показала (здесь я их не привожу), что время трансформации растет пропорционально объему XML, превращаемого во временное дерево.

Замечу, что libxslt и MSXML на этом и последующих графиках не соответствуют принятому масштабу, потому что иначе на фоне Java-процессоров они превращаются в один пиксель и на них ничего не видно. При этом числовые значения на выносках, разумеется, сохранены такими, какими они были получены при замерах.

Тест № 2 — вывод XML-атрибутов

Тест основан на задаче вывода атрибута с предварительной проверкой, не пуст ли он. Эта задача обсуждалась в применении № 2.

Без временных деревьев:

<xsl:template match="/">
    <div>
        <xsl:attribute name="id">megadiv</xsl:attribute>
        <xsl:attribute name="class">slogan</xsl:attribute>
        <xsl:text>Дизайн спасет мир</xsl:text>
    </div>
</xsl:template>

На временных деревьях:

<xsl:template match="/">
    <div>
        <xsl:call-template name="attributes">
            <xsl:with-param name="set">
                <id>megadiv</id>
                <class>slogan</class>
            </xsl:with-param>
        </xsl:call-template>
        <xsl:text>Дизайн спасет мир</xsl:text>
    </div>
</xsl:template>

<xsl:template name="attributes">
    <xsl:param name="set" />
    <xsl:for-each select="exsl:node-set($set)/*">
        <xsl:if test="normalize-space(.)">
            <xsl:attribute name="{name()}">
                <xsl:value-of select="normalize-space(.)" />
            </xsl:attribute>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

Чтобы увидеть хоть какую-то разницу, этот код был выполнен не единожды, а 100 раз в цикле.

Результаты теста № 2
Результаты теста № 2

Видно, что на libxslt и MSXML использование временных деревьев никак не повлияло (или это влияние ничтожно мал? и незаметно). А вот Java-трансформаторы явно замедлились.

У Saxon хочу обратить наше внимание на то, что во втором столбике время парсинга XML тоже выросло. Это довольно неожиданно, так как XML не менялся вообще. Также выросло и время парсинга XSL, что связано с более сложным XSL-кодом.

Тест № 3 — статические данные в коде шаблона

Этот тест навеян применением № 1. Там мы выводили контрол размножения полей, состоящий из четырех кнопок.

Левый столбик представлен кодом, который хардкорно выводит эти кнопки одну за другой:

<xsl:template match="/">
    <xsl:call-template name="repeat_control" />
</xsl:template>

<xsl:template name="repeat_control">
    <div class="repeat_control">
        <input type="button" class="append">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'append_button']">
                        <xsl:value-of select="//label[@for = 'append_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>+<!-- плюс --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="remove">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'remove_button']">
                        <xsl:value-of select="//label[@for = 'remove_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>?<!-- минус --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="up">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'up_button']">
                        <xsl:value-of select="//label[@for = 'up_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>^<!-- вверх --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
        <input type="button" class="down">
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="//label[@for = 'down_button']">
                        <xsl:value-of select="//label[@for = 'down_button'][1]" />
                    </xsl:when>
                    <xsl:otherwise>v<!-- вниз --></xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </input>
    </div>
</xsl:template>

В правом столбике происходит динамический обход временного дерева, хранящего 4 элемента :

<xsl:template match="/">
    <xsl:call-template name="repeat_control" />
</xsl:template>

<xsl:variable name="repeat_control_buttons">
    <button name="append" label="+" />
    <button name="remove" label="?" />
    <button name="up" label="^" />
    <button name="down" label="v" />
</xsl:variable>

<xsl:variable
    name="repeat_control_buttons_set"
    select="exsl:node-set($repeat_control_buttons)" />

<xsl:variable name="input_root" select="/" />

<xsl:template name="repeat_control">
    <div class="repeat_control">
        <xsl:for-each select="$repeat_control_buttons_set/button">
            <input type="button" class="{@name}">
                <xsl:attribute name="value">
                    <xsl:variable
                        name="custom_label"
                        select="$input_root//label[@for = concat(current()/@name, '_button')][1]" />
                    <xsl:choose>
                        <!-- Если есть пользовательский лейбл, выводим его -->
                        <xsl:when test="$custom_label">
                            <xsl:value-of select="$custom_label" />
                        </xsl:when>
                        <!-- Если нет – выводим лейбл по умолчанию -->
                        <xsl:otherwise>
                            <xsl:value-of select="@label" />
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:attribute>
            </input>
        </xsl:for-each>
    </div>
</xsl:template>

Результаты теста № 3
Результаты теста № 3

Тест № 4 — оборачивание произвольного контента

Нам нужно обернуть некий заранее неизвестный контент определенной разметкой, но только если этот контент не пустой (см. применение № 2).

В первом варианте делаем это по старинке:

<xsl:template match="/">
    <xsl:variable name="content">
        <xsl:call-template name="content" />
    </xsl:variable>

    <xsl:if test="normalize-space($content)">
        <div class="Product">
            <h2>Продукт</h2>
            <xsl:copy-of select="$content" />
        </div>
    </xsl:if>
</xsl:template>

<xsl:template name="content">
    <xsl:copy-of select="/descendant::Product[1]" />
</xsl:template>

Во втором варианте задействуем наши вспомогательные шаблоны, использующие временное дерево:

<xsl:template match="/">
    <xsl:call-template name="wrap">
        <xsl:with-param name="content">
            <xsl:call-template name="content" />
        </xsl:with-param>
        <xsl:with-param name="wrap">
            <div class="Product">
                <h2>Продукт</h2>
                <put_content_here />
            </div>
        </xsl:with-param>
    </xsl:call-template>
</xsl:template>

<xsl:template name="content">
    <xsl:copy-of select="/descendant::Product[1]" />
</xsl:template>


<xsl:template name="wrap">
    <xsl:param name="content" />
    <xsl:param name="wrap" />

    <!-- Что-то выводим, только если контент не пуст -->
    <xsl:if test="normalize-space($content)">
        <xsl:apply-templates select="exsl:node-set($wrap)/node()" mode="wrap_copy">
            <xsl:with-param name="content" select="$content" />
        </xsl:apply-templates>
    </xsl:if>
</xsl:template>

<!-- Полностью копируем разметку обертки -->
<xsl:template match="*" mode="wrap_copy">
    <xsl:param name="content" />

    <xsl:element name="{name()}">
        <xsl:copy-of select="@*" />
        <xsl:apply-templates mode="wrap_copy">
            <xsl:with-param name="content" select="$content" />
        </xsl:apply-templates>
    </xsl:element>
</xsl:template>

<!-- Но элемент <put_content_here /> подменяем на контент из параметра $content -->
<xsl:template match="put_content_here" mode="wrap_copy">
    <xsl:param name="content" />
    <xsl:copy-of select="$content" />
</xsl:template>

В match="/" продукт был выведен 100 раз. Результаты замеров:

Результаты теста № 4
Результаты теста № 4

Тест № 5 — подстройка под готовый шаблон

В последнем тесте мы хотим вывести файл для скачивания, используя для этого типовой шаблон.

Сначала делаем это очевидным способом (см. применение № 4):

<xsl:template match="/">
    <xsl:apply-templates select="/descendant::file[1]" mode="html" />
</xsl:template>

<xsl:template match="file[@src and (string(.) or @name)]" mode="html">
    <span class="file {@ext}">
        <a href="{@src}" target="_blank">
            <i />
            <xsl:choose>
                <xsl:when test="string(.)">
                    <xsl:value-of select="." />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="@name" />
                </xsl:otherwise>
            </xsl:choose>
        </a>
        <nobr>
            <xsl:value-of select="concat(@ext, ', ', @size)"/>
        </nobr>
    </span>
</xsl:template>

Во втором случае подразумеваем, что формат входящего XML изменился и мы подстраиваемся под уже написанный нами шаблон:

<xsl:template match="/">
    <xsl:variable name="input_file" select="/descendant::file[1]" />
    <xsl:variable name="my_file">
        <file
            src="{$input_file/@src}"
            ext="{$input_file/@ext}"
            name="{$input_file/@name}"
            size="{$input_file/@size}" />
    </xsl:variable>

    <xsl:apply-templates select="exsl:node-set($my_file)/file" />
</xsl:template>


<xsl:template match="file[@src and (string(.) or @name)]" mode="html">
    <span class="file {@ext}">
        <a href="{@src}" target="_blank">
            <i />
            <xsl:choose>
                <xsl:when test="string(.)">
                    <xsl:value-of select="." />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="@name" />
                </xsl:otherwise>
            </xsl:choose>
        </a>
        <nobr>
            <xsl:value-of select="concat(@ext, ', ', @size)"/>
        </nobr>
    </span>
</xsl:template>

Результаты теста № 5
Результаты теста № 5

Наблюдаем картину, аналогичную предыдущему тесту, — libxslt и MSXML все нипочем, а «Java-товарищи» опять слегка просели по времени, однако вызвано это в большей степени удлинением парсинга XSL.

Очевидный вывод: временные деревья замедляют XSL-трансформацию. Однако для libxslt и MSXML это замедление настолько мал?, что в наших тестах не превышает и 2 миллисекунд. Java-трансформаторы определенно более чувствительны, хотя их замедление тоже не является критичным, кроме аномального поведения Xalan в тесте № 3.

Б?льшая часть этих тестов моделирует реальные жизненные задачи, поэтому ситуация, когда использование временного дерева станет фактором сильного снижения производительности сайта, на мой взгляд, маловероятна.

В следующей, последней, части мы обсудим тонкости временных деревьев.

Временные деревья в XSLT

  1. Часть первая. Что такое временное дерево
  2. Часть вторая. Применение временных деревьев № 1 — хранение статических данных в коде XSL-шаблона
  3. Часть третья. Применение временных деревьев № 2 — передача сложных параметров
  4. Часть четвертая. Применение временных деревьев № 3 — шаблонизация однотипных строк
  5. Часть пятая. Применение временных деревьев № 4 — подстройка под готовый шаблон
  6. Часть шестая. Применение временных деревьев № 5 — многоступенчатая трансформация
  7. Часть седьмая. О трансформаторах, версиях XSLT и переносимости кода, используещего временные деревья
  8. Часть восьмая. О производительности при работе с временными деревьями
  9. Часть девятая. Тонкости использования временных деревьев


14 июля 2011 г.
Александр Самиляк
http://www.artlebedev.ru/tools/technogrette/xslt/temptrees-8/