Оптимизация хвостовых вызовов в XSLT / Saxon? - PullRequest
0 голосов
/ 15 октября 2019

Может кто-нибудь объяснить, почему приведенный ниже код получает переполнение стека? Я надеялся, что Saxon идентифицирует шаблон как хвостовую рекурсивную и оптимизирует его, допуская очень большое количество итераций - в действительности он получает переполнение стека после ~ 1000 итераций. Я выполняю, как показано ниже:

me@server:~/dev$ java -classpath /usr/local/share/java/saxon9ee.jar net.sf.saxon.Transform -it -xsl:recurse.xslt 
437
Exception in thread "main" java.lang.StackOverflowError
        at net.sf.saxon.expr.instruct.ParameterSet.getIndex(ParameterSet.java:127)
        at net.sf.saxon.expr.XPathContextMajor.useLocalParameter(XPathContextMajor.java:561)
        at EE_sequence_02125238280.process(file:/home/me/dev/recurse.xslt:23)
        at com.saxonica.ee.bytecode.CompiledExpression.process(CompiledExpression.java:84)
        at com.saxonica.ee.bytecode.ByteCodeCandidate.process(ByteCodeCandidate.java:143)
        at com.saxonica.ee.bytecode.ByteCodeCandidate.processLeavingTail(ByteCodeCandidate.java:178)
        at net.sf.saxon.expr.instruct.NamedTemplate.expand(NamedTemplate.java:263)
        at EE_sequence_02125238280.process(file:/home/me/dev/recurse.xslt:23)
and so on.....

Я использую Saxon-EE 9.8.0.15J.

Я пытался использовать <xsl:if>, XPATH и функции в несколькихварианты вместо <xsl:choose> - но я получаю ту же проблему.

С call-templates я могу фактически найти комментарии онлайн, предлагающие это, с примерами, подобными моим ниже. Я не был на 100% уверен, поддерживаются ли функции или рекурсивный вызов внутри выражения XPATH, поэтому я остановился на call-templates для этого примера. Например: Рекурсивный цикл XSLT

Полагаю, мне не хватает трюка - есть идеи?

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                                                             
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:map="http://www.w3.org/2005/xpath-functions/map"  exclude-result-prefixes="xs map">               

    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>                                                                                                                                                        

    <xsl:template name="xsl:initial-template">                                                                                                                                                                                     
        <xsl:variable name="freqs" select="unparsed-text-lines('input.txt', 'UTF-8')!xs:integer(.)"/>                                                                                                                              
        <xsl:message select="sum($freqs)"/>                                                                                                                                                                                        
        <xsl:variable name="hash" select="map{}" as="map(xs:integer, xs:boolean)"/>                                                                                                                                                
        <xsl:call-template name="find-repeated-cs">                                                                                                                                                                                
            <xsl:with-param name="freqs" select="$freqs"/>                                                                                                                                                                         
            <xsl:with-param name="cs-hash" select="$hash"/>                                                                                                                                                                        
        </xsl:call-template>                                                                                                                                                                                                       
    </xsl:template>                                                                                                                                                                                                                

    <xsl:template name="find-repeated-cs">                                                                                                                                                                                         
        <xsl:param name="freqs" as="xs:integer*"/>                                                                                                                                                                                 
        <xsl:param name="cs-hash" as="map(xs:integer, xs:boolean)"/>                                                                                                                                                               
        <xsl:param name="cs" select="0" as="xs:integer"/>                                                                                                                                                                          
        <xsl:param name="i" select="1" as="xs:integer"/>                                                                                                                                                                           
        <xsl:variable name="new-cs" select="$cs + $freqs[$i]" as="xs:integer"/>                                                                                                                                                    
        <xsl:variable name="new-i" select="if ($i >= count($freqs)) then 1 else $i + 1" as="xs:integer"/>                                                                                                                          
        <xsl:choose>                                                                                                                                                                                                               
            <xsl:when test="map:contains($cs-hash, $new-cs)">                                                                                                                                                                      
                <xsl:value-of select="$new-cs"/>                                                                                                                                                                                   
            </xsl:when>                                                                                                                                                                                                            
            <xsl:otherwise>                                                                                                                                                                                                        
                <xsl:call-template name="find-repeated-cs">                                                                                                                                                                        
                    <xsl:with-param name="freqs" select="$freqs"/>                                                                                                                                                                 
                    <xsl:with-param name="cs-hash" select="map:put($cs-hash,$new-cs,true())"/>                                                                                                                                     
                    <xsl:with-param name="cs" select="$new-cs"/>                                                                                                                                                                   
                    <xsl:with-param name="i" select="$new-i"/>                                                                                                                                                                     
                </xsl:call-template>                                                                                                                                                                                               
            </xsl:otherwise>                                                                                                                                                                                                       
        </xsl:choose>                                                                                                                                                                                                              
    </xsl:template>                                                                                                                                                                                                                
</xsl:stylesheet>

РЕДАКТИРОВАТЬ

Для некоторого контекста код находит второе вхождение числа в последовательности накопленной суммы, сгенерированной из многократного циклического повторения по фиксированному набору целых чисел freqs. Последняя накопленная сумма составляет cs, а словарь прошлых накопленных сумм составлен в cs-hash. i indexes freq как циклический индекс / счетчик.

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

РЕДАКТИРОВАТЬ 2

Для полноты реализации функции, используя xsl:choose:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                            
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:aoc2018="\
http://www.blah.co.uk/aoc2018" exclude-result-prefixes="xs map aoc2018">                                                                                                                          
    <!-- hint: java -classpath /usr/local/share/java/saxon9ee.jar net.sf.saxon.Transform -it -xsl:01.xslt -->                                                                                     
    <xsl:function name="aoc2018:find-repeated-cs">                                                                                                                                                
        <xsl:param name="freqs" as="xs:integer*"/>                                                                                                                                                
        <xsl:param name="cs-hash" as="map(xs:integer, xs:boolean)"/>                                                                                                                              
        <xsl:param name="cs" as="xs:integer"/>                                                                                                                                                    
        <xsl:param name="i" as="xs:integer"/>                                                                                                                                                     
        <xsl:variable name="new-cs" select="$cs + $freqs[$i]" as="xs:integer"/>                                                                                                                   
        <xsl:choose>                                                                                                                                                                              
            <xsl:when test="map:contains($cs-hash, $new-cs)">                                                                                                                                     
                <xsl:value-of select="$new-cs"/>                                                                                                                                                  
            </xsl:when>                                                                                                                                                                           
            <xsl:otherwise>                                                                                                                                                                       
                <xsl:variable name="new-i" select="if ($i >= count($freqs))                                                                                                                       
                                                   then 1                                                                                                                                         
                                                   else $i + 1" as="xs:integer"/>                                                                                                                 
                <xsl:value-of select="aoc2018:find-repeated-cs($freqs, map:put($cs-hash,$new-cs,true()), $new-cs, $new-i)"/>                                                                      
            </xsl:otherwise>                                                                                                                                                                      
        </xsl:choose>                                                                                                                                                                             
    </xsl:function>                                                                                                                                                                               
    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>                                                                                                                       
    <xsl:template name="xsl:initial-template">                                                                                                                                                    
        <xsl:variable name="freqs" select="unparsed-text-lines('input.txt', 'UTF-8')!xs:integer(.)"/>                                                                                             
        <xsl:message select="sum($freqs)"/>                                                                                                                                                       
        <xsl:variable name="hash" select="map{}" as="map(xs:integer, xs:boolean)"/>                                                                                                               
        <xsl:message select="aoc2018:find-repeated-cs($freqs, $hash, 0, 1)"/>                                                                                                                     
    </xsl:template>                                                                                                                                                                               
</xsl:stylesheet>     

И функциюреализация с использованием XPATH:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                            
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:aoc2018="\
http://www.blah.co.uk/aoc2018" exclude-result-prefixes="xs map aoc2018">                                                                                                                          
    <!-- hint: java -classpath /usr/local/share/java/saxon9ee.jar net.sf.saxon.Transform -it -xsl:01.xslt -->                                                                                     
    <xsl:function name="aoc2018:find-repeated-cs">                                                                                                                                                
        <xsl:param name="freqs" as="xs:integer*"/>                                                                                                                                                
        <xsl:param name="cs-hash" as="map(xs:integer, xs:boolean)"/>                                                                                                                              
        <xsl:param name="cs" as="xs:integer"/>                                                                                                                                                    
        <xsl:param name="i" as="xs:integer"/>                                                                                                                                                     
        <xsl:variable name="new-cs" select="$cs + $freqs[$i]" as="xs:integer"/>                                                                                                                   
        <xsl:variable name="new-i" select="if ($i >= count($freqs))                                                                                                                               
                                                   then 1                                                                                                                                         
                                                   else $i + 1" as="xs:integer"/>                                                                                                                 
        <xsl:value-of select="if (map:contains($cs-hash, $new-cs))                                                                                                                                
                              then $new-cs                                                                                                                                                        
                              else aoc2018:find-repeated-cs($freqs, map:put($cs-hash,$new-cs,true()), $new-cs, $new-i)"/>                                                                         
    </xsl:function>                                                                                                                                                                               
    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>                                                                                                                       
    <xsl:template name="xsl:initial-template">                                                                                                                                                    
        <xsl:variable name="freqs" select="unparsed-text-lines('input.txt', 'UTF-8')!xs:integer(.)"/>                                                                                             
        <xsl:message select="sum($freqs)"/>                                                                                                                                                       
        <xsl:variable name="hash" select="map{}" as="map(xs:integer, xs:boolean)"/>                                                                                                               
        <xsl:message select="aoc2018:find-repeated-cs($freqs, $hash, 0, 1)"/>                                                                                                                     
    </xsl:template>                                                                                                                                                                               
</xsl:stylesheet>    

1 Ответ

2 голосов
/ 15 октября 2019

Подтверждено, что это работает (с хвостовой рекурсией) под 9.9, с включенной или без генерации байт-кода, но под 9.8 это будет успешным, только если генерация байт-кода отключена. Я думаю, что разница между выпусками заключается в том, что 9.9 умнее при принятии решения не использовать генерацию байт-кода в тех случаях, когда это будет мешать хвостовой рекурсии.

Чтобы понять, почему происходит сбой при использовании вызовов функций, а не шаблонов, которые мне нужнычтобы увидеть код. Два случая используют разные механизмы внутри. В частности, функции по умолчанию оцениваются в режиме «тянуть» (они возвращают итератор по результату), а шаблоны - в режиме «толкать» (они записывают результаты в дерево результатов). Наиболее заметным отличием является то, что возврат последовательности, содержащей результат рекурсивного вызова (например, select="$x, f:myself($x - 1)), может выполняться рекурсивно с использованием шаблонов, но не функций. Но это, похоже, не относится к вашему делу. Кроме того, для шаблонов мы обрабатываем взаимную рекурсию двух или более шаблонов, в то время как с функциями мы обрабатываем только саморекурсию.

Следующая версия, по-видимому, работает с использованием хвостовой рекурсии с использованием 9.8 или 9.9, с или без байт-кодапоколение. (Тем не менее, в 9.8 есть странность, которую я не успел выяснить: после получения выходного значения процесс фактически не завершается.)

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                                                             
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:f="f" exclude-result-prefixes="#all">               

    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:param name="limit" select="2000"/>

    <xsl:template name="xsl:initial-template">                                                                                                                                                                                     
        <xsl:variable name="freqs" select="1 to $limit"/>                                                                                                                              
        <xsl:message select="sum($freqs)"/>                                                                                                                                                                                        
        <xsl:variable name="hash" select="map{}" as="map(xs:integer, xs:boolean)"/>   
        <xsl:sequence select="f:find-repeated-cs($freqs, $hash, 0, 1)"/>                                                                                                                                                                                                 
    </xsl:template>                                                                                                                                                                                                                

    <xsl:function name="f:find-repeated-cs">                                                                                                                                                                                         
        <xsl:param name="freqs" as="xs:integer*"/>                                                                                                                                                                                 
        <xsl:param name="cs-hash" as="map(xs:integer, xs:boolean)"/>                                                                                                                                                               
        <xsl:param name="cs" as="xs:integer"/>                                                                                                                                                                          
        <xsl:param name="i" as="xs:integer"/>                                                                                                                                                                           
        <xsl:variable name="new-cs" select="$cs + $freqs[$i]" as="xs:integer"/>                                                                                                                                                    
        <xsl:variable name="new-i" select="if ($i >= count($freqs)) then 1 else $i + 1" as="xs:integer"/>                                                                                                                          
        <xsl:choose>                                                                                                                                                                                                               
            <xsl:when test="map:contains($cs-hash, $new-cs)">                                                                                                                                                                      
                <xsl:value-of select="$new-cs"/>                                                                                                                                                                                   
            </xsl:when>                                                                                                                                                                                                            
            <xsl:otherwise>                                                                                                                                                                                                        
                <xsl:sequence select="f:find-repeated-cs($freqs, map:put($cs-hash,$new-cs,true()), $new-cs, $new-i)"/>                                                                                                                                                                                                                                                                                                                                                                   
            </xsl:otherwise>                                                                                                                                                                                                       
        </xsl:choose>                                                                                                                                                                                                              
    </xsl:function>                                                                                                                                                                                                                
</xsl:stylesheet>

ОБНОВЛЕНИЕ

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

В вашем коде вы использовали <xsl:value-of> для возврата результата функции, а не xsl:sequence. xsl:value-of доставляет текстовый узел, который должен быть построен из результата рекурсивного вызова: вы можете увидеть это в выводе -explain:

<function name="Q{http://www.blah.co.uk/aoc2018}find-repeated-cs"
              line="5"
              module="file:/Users/mike/Desktop/temp/test.xsl"
              eval="9"
              flags="pU"
              as="item()*"
              slots="6">
      <arg name="Q{}freqs" as="xs:integer*"/>
      <arg name="Q{}cs-hash" as="map(xs:integer, xs:boolean)"/>
      <arg name="Q{}cs" as="xs:integer"/>
      <arg name="Q{}i" as="xs:integer"/>
      <let role="body"
           baseUri="file:/Users/mike/Desktop/temp/test.xsl"
           ns="xsl=~ aoc2018=http://www.blah.co.uk/aoc2018 xs=~ map=~"
           line="10"
           var="Q{}new-cs"
           as="xs:integer"
           slot="4"
           eval="16">
        <check card="1" diag="3|0|XTTE0570|new-cs">
          <arith op="+" calc="i+i">
            <varRef name="Q{}cs" slot="2"/>
            <subscript>
              <varRef name="Q{}freqs" slot="0"/>
              <varRef name="Q{}i" slot="3"/>
            </subscript>
          </arith>
        </check>
        <let line="13" var="Q{}new-i" as="xs:integer" slot="5" eval="16">
          <choose>
            <vc op="ge" onEmpty="0" comp="CAVC">
              <varRef name="Q{}i" slot="3"/>
              <fn name="count">
                <varRef name="Q{}freqs" slot="0"/>
              </fn>
            </vc>
            <int val="1"/>
            <true/>
            <arith op="+" calc="i+i">
              <varRef name="Q{}i" slot="3"/>
              <int val="1"/>
            </arith>
          </choose>
          <valueOf line="16">
            <fn name="string-join">
              <convert from="xs:anyAtomicType" to="xs:string">
                <choose>
                  <ifCall name="Q{http://www.w3.org/2005/xpath-functions/map}contains"
                          type="xs:boolean">
                    <varRef name="Q{}cs-hash" slot="1"/>
                    <varRef name="Q{}new-cs" slot="4"/>
                  </ifCall>
                  <varRef name="Q{}new-cs" slot="4"/>
                  <true/>
                  <data>
                    <mergeAdj>
                      <ufCall name="Q{http://www.blah.co.uk/aoc2018}find-repeated-cs"
                              tailCall="false"
                              bSlot="0"
                              eval="6 16 6 6">
                        <varRef name="Q{}freqs" slot="0"/>
                        <treat as="map(xs:integer, xs:boolean)" diag="0|1||aoc2018:find-repeated-cs">
                          <ifCall name="Q{http://www.w3.org/2005/xpath-functions/map}put" type="map(*)">
                            <varRef name="Q{}cs-hash" slot="1"/>
                            <varRef name="Q{}new-cs" slot="4"/>
                            <true/>
                          </ifCall>
                        </treat>
                        <varRef name="Q{}new-cs" slot="4"/>
                        <varRef name="Q{}new-i" slot="5"/>
                      </ufCall>
                    </mergeAdj>
                  </data>
                </choose>
              </convert>
              <str val=" "/>
            </fn>
          </valueOf>
        </let>
      </let>
    </function>

Поскольку дальнейшие операции необходимо выполнить с функциейрезультат (а именно, преобразование его в текстовый узел, который включает в себя дробление результата, преобразование элементов в результате в строки и разделение их пробелами), функция не является хвостовой рекурсивной. Всегда используйте xsl:sequence для возврата результата функции и всегда объявляйте тип результата функции и типы параметров.

...