Fully tail-recursive mrange
This solves issues of hitting stack limits, particularly in browsers, when querying matrices that return a large number of rows for one or more predicates.master
commit
e27423f909
24
RELEASES.md
24
RELEASES.md
|
@ -13,9 +13,31 @@ TAME developers: Add new changes under a "NEXT" heading as part of the
|
|||
commits that introduce the changes. To make a new release, run
|
||||
`tools/mkrelease`, which will handle updating the heading for you.
|
||||
|
||||
NEXT
|
||||
====
|
||||
This release provides tail-call optimizations aimed at the query system in
|
||||
core.
|
||||
|
||||
Compiler
|
||||
--------
|
||||
- [bugfix] Recursive calls using TCO will wait to overwrite their function
|
||||
arguments until all expressions calculating the new argument values have
|
||||
completed.
|
||||
|
||||
`tame-core`
|
||||
-----------
|
||||
- `mrange` is now fully tail-recursive and has experimental TCO applied.
|
||||
- It was previously only recursive for non-matching rows.
|
||||
|
||||
|
||||
v17.6.5 (2020-12-03)
|
||||
====================
|
||||
Improve summary page performance with new element queries.
|
||||
This release improves Summary Page performance when populating the page with
|
||||
data loaded from an external source.
|
||||
|
||||
Summary Page
|
||||
------------
|
||||
- Populating the DOM with loaded data now runs in linear time.
|
||||
|
||||
|
||||
v17.6.4 (2020-11-23)
|
||||
|
|
|
@ -246,6 +246,102 @@
|
|||
<param name="seq" type="boolean" desc="Is data sequential?" />
|
||||
<param name="op" type="integer" desc="Comparison operator" />
|
||||
|
||||
<c:let>
|
||||
<c:values>
|
||||
<c:value name="matches" type="integer" set="vector"
|
||||
desc="Matching row indexes of matrix">
|
||||
<c:apply name="mrange_accum"
|
||||
matrix="matrix" col="col" val="val"
|
||||
start="start" end="end" seq="seq" op="op">
|
||||
<!-- Matchines indexes will be accumulated into a vector (in
|
||||
reverse) to permit TCO -->
|
||||
<c:arg name="accum">
|
||||
<c:vector />
|
||||
</c:arg>
|
||||
</c:apply>
|
||||
</c:value>
|
||||
</c:values>
|
||||
|
||||
<c:apply name="_mextract_rows" matrix="matrix" indexes="matches" i="#0">
|
||||
<!-- Pre-compute so _mextract_rows doesn't have to -->
|
||||
<c:arg name="length">
|
||||
<c:length-of>
|
||||
<c:value-of name="matches" />
|
||||
</c:length-of>
|
||||
</c:arg>
|
||||
|
||||
<!-- The final matrix will be accumulated to permit TCO (note that
|
||||
this reverse the original reversal mentioned above, so the
|
||||
final matrix is in the right order) -->
|
||||
<c:arg name="accum">
|
||||
<c:vector />
|
||||
</c:arg>
|
||||
</c:apply>
|
||||
</c:let>
|
||||
</function>
|
||||
|
||||
|
||||
<function name="_mextract_rows"
|
||||
desc="Pull rows from a matrix by index">
|
||||
<param name="matrix" type="float" set="matrix" desc="Source matrix" />
|
||||
<param name="indexes" type="integer" set="vector" desc="Indexes to extract" />
|
||||
<param name="length" type="integer" desc="Length of indexes vector" />
|
||||
<param name="i" type="integer" desc="Current index offset" />
|
||||
<param name="accum" type="float" set="matrix" desc="Accumulator (matrix)" />
|
||||
|
||||
<param name="__experimental_guided_tco" type="float" desc="Experimental guided TCO" />
|
||||
|
||||
<c:cases>
|
||||
<!-- When we're done, yield the accumulated value, representign our
|
||||
final matrix -->
|
||||
<c:case>
|
||||
<t:when-eq name="i" value="length" />
|
||||
<c:value-of name="accum" />
|
||||
</c:case>
|
||||
|
||||
<c:otherwise>
|
||||
<c:recurse __experimental_guided_tco="TRUE">
|
||||
<c:arg name="i">
|
||||
<c:sum>
|
||||
<c:value-of name="i" />
|
||||
<c:const value="1" desc="Proceed to next index" />
|
||||
</c:sum>
|
||||
</c:arg>
|
||||
|
||||
<c:arg name="accum">
|
||||
<c:cons>
|
||||
<!-- Add the row identified by the current index to the
|
||||
accumulator. Note that this uses cons, so it adds it
|
||||
to the head, but since the original mrange results are
|
||||
reversed, this is precisely what we want—to reverse the
|
||||
reversal -->
|
||||
<c:value-of name="matrix">
|
||||
<c:index>
|
||||
<c:value-of name="indexes" index="i" />
|
||||
</c:index>
|
||||
</c:value-of>
|
||||
|
||||
<c:value-of name="accum" />
|
||||
</c:cons>
|
||||
</c:arg>
|
||||
</c:recurse>
|
||||
</c:otherwise>
|
||||
</c:cases>
|
||||
</function>
|
||||
|
||||
|
||||
<function name="mrange_accum"
|
||||
desc="Filter matrix rows by column value within a certain
|
||||
range of indexes (inclusive)">
|
||||
<param name="matrix" type="float" set="matrix" desc="Matrix to filter" />
|
||||
<param name="col" type="integer" desc="Column index to filter on" />
|
||||
<param name="val" type="float" desc="Column value to filter on" />
|
||||
<param name="start" type="integer" desc="Starting index (inclusive)" />
|
||||
<param name="end" type="integer" desc="Ending index (inclusive)" />
|
||||
<param name="seq" type="boolean" desc="Is data sequential?" />
|
||||
<param name="op" type="integer" desc="Comparison operator" />
|
||||
<param name="accum" type="integer" set="vector" desc="Accumulator (row indexes)" />
|
||||
|
||||
<param name="__experimental_guided_tco" type="float" desc="Experimental guided TCO" />
|
||||
|
||||
<c:let>
|
||||
|
@ -283,7 +379,7 @@
|
|||
<c:case>
|
||||
<t:when-gt name="start" value="end" />
|
||||
|
||||
<c:vector />
|
||||
<c:value-of name="accum" />
|
||||
</c:case>
|
||||
|
||||
<!-- if the data is sequential and the next element is over the
|
||||
|
@ -293,7 +389,7 @@
|
|||
<t:when-lte name="op" value="CMP_OP_LTE" />
|
||||
<t:when-eq name="over" value="TRUE" />
|
||||
|
||||
<c:vector />
|
||||
<c:value-of name="accum" />
|
||||
</c:case>
|
||||
|
||||
|
||||
|
@ -364,45 +460,39 @@
|
|||
</c:value>
|
||||
</c:values>
|
||||
|
||||
<c:cases>
|
||||
<!-- if values matches, cons it -->
|
||||
<c:case>
|
||||
<t:when-eq name="found" value="TRUE" />
|
||||
<!-- continue recursion using TCO so that we do not
|
||||
exhaust the stack (this is an undocumented,
|
||||
experimental feature that requires explicitly stating
|
||||
that a recursive call is in tail position) -->
|
||||
<c:recurse __experimental_guided_tco="TRUE">
|
||||
<c:arg name="accum">
|
||||
<c:cases>
|
||||
<!-- If match, add the current row index to the
|
||||
accumulator (cons, so note that it is added
|
||||
in reverse) -->
|
||||
<c:case>
|
||||
<t:when-eq name="found" value="TRUE" />
|
||||
|
||||
<c:cons>
|
||||
<c:value-of name="matrix">
|
||||
<c:index>
|
||||
<c:cons>
|
||||
<c:value-of name="start" />
|
||||
</c:index>
|
||||
</c:value-of>
|
||||
<c:value-of name="accum" />
|
||||
</c:cons>
|
||||
</c:case>
|
||||
|
||||
<c:recurse>
|
||||
<c:arg name="start">
|
||||
<c:sum>
|
||||
<c:value-of name="start" />
|
||||
<c:const value="1" desc="Check next element" />
|
||||
</c:sum>
|
||||
</c:arg>
|
||||
</c:recurse>
|
||||
</c:cons>
|
||||
</c:case>
|
||||
<!-- If no match, no change to accumulator -->
|
||||
<c:otherwise>
|
||||
<c:value-of name="accum" />
|
||||
</c:otherwise>
|
||||
</c:cases>
|
||||
</c:arg>
|
||||
|
||||
|
||||
<!-- no match, continue recursion using TCO so that we
|
||||
do not exhaust the stack (this is an undocumented,
|
||||
experimental feature that requires explicitly
|
||||
stating that a recursive call is in tail position) -->
|
||||
<c:otherwise>
|
||||
<c:recurse __experimental_guided_tco="TRUE">
|
||||
<c:arg name="start">
|
||||
<c:sum>
|
||||
<c:value-of name="start" />
|
||||
<c:const value="1" desc="Check next element" />
|
||||
</c:sum>
|
||||
</c:arg>
|
||||
</c:recurse>
|
||||
</c:otherwise>
|
||||
</c:cases>
|
||||
<c:arg name="start">
|
||||
<c:sum>
|
||||
<c:value-of name="start" />
|
||||
<c:const value="1" desc="Check next element" />
|
||||
</c:sum>
|
||||
</c:arg>
|
||||
</c:recurse>
|
||||
</c:let>
|
||||
</c:let>
|
||||
</c:otherwise>
|
||||
|
|
|
@ -960,27 +960,42 @@
|
|||
|
||||
<variable name="arg-prefix" select="concat( ':', $name, ':' )" />
|
||||
|
||||
<text>(/*TCO*/function(){</text>
|
||||
|
||||
<!-- reassign function arguments -->
|
||||
<for-each select="
|
||||
root(.)/preproc:symtable/preproc:sym[
|
||||
@type='func'
|
||||
and @name=$name
|
||||
]/preproc:sym-ref
|
||||
">
|
||||
<variable name="args" as="element(c:arg)*">
|
||||
<for-each select="
|
||||
root(.)/preproc:symtable/preproc:sym[
|
||||
@type='func'
|
||||
and @name=$name
|
||||
]/preproc:sym-ref
|
||||
">
|
||||
|
||||
<variable name="pname" select="substring-after( @name, $arg-prefix )" />
|
||||
<variable name="arg" select="$self/c:arg[@name=$pname]" />
|
||||
<variable name="pname" select="substring-after( @name, $arg-prefix )" />
|
||||
<variable name="arg" select="$self/c:arg[@name=$pname]" />
|
||||
|
||||
<!-- if the call specified this argument, then use it -->
|
||||
<if test="$arg">
|
||||
<sequence select="concat( '/*TCO*/', $pname, '=' )" />
|
||||
<apply-templates select="$arg/c:*[1]" mode="compile" />
|
||||
<text>,</text>
|
||||
</if>
|
||||
<!-- if the call specified this argument, then use it -->
|
||||
<sequence select="$arg" />
|
||||
</for-each>
|
||||
</variable>
|
||||
|
||||
<!-- store reassignments first in a temporary variable, since the
|
||||
expressions may reference the original arguments and we do not want
|
||||
to overwrite yet -->
|
||||
<for-each select="$args">
|
||||
<sequence select="concat( 'const __tco_', @name, '=' )" />
|
||||
<apply-templates select="c:*[1]" mode="compile" />
|
||||
<text>;</text>
|
||||
</for-each>
|
||||
|
||||
<!-- perform final reassignments, now that expressions no longer need the
|
||||
original values -->
|
||||
<for-each select="$args">
|
||||
<sequence select="concat( @name, '=__tco_', @name, ';' )" />
|
||||
</for-each>
|
||||
|
||||
<!-- return value, which doesn't matter since it won't be used -->
|
||||
<text>0</text>
|
||||
<text>return 0;})()</text>
|
||||
|
||||
<!-- don't support c:when here; not worth the effort -->
|
||||
<if test="./c:when">
|
||||
|
|
Loading…
Reference in New Issue