• Keine Ergebnisse gefunden

Runtime Optimizations

3.3 Optimizations

3.3.3 Runtime Optimizations

All static optimizations, which have been presented throughout this section, have con-stant or logarithmic costs and will be cheaper than the query evaluation step, at least in

3.3. Optimizations most cases. Next to that, they will only be performed once at compile time. As not all properties of the touched data will be known before the query is evaluated – or cannot be used for optimizations – some decisions have to be taken at runtime, orjust-in-time.

As an example, a sequence might include items of different types, and an expression will need to choose between different evaluation plans at runtime. Next, the contents of a document may be used as input for another doc() function, as the following example demonstrates:

for $db in doc(’list.xml’)//database/@name return doc($db)

Dynamic query processing is a well-known topic in the database world; see [GW89] for an important contribution on dynamic evaluation plans. A major challenge is to quickly decide which evaluation alternative will possibly yield the fastest results: otherwise, the costs for finding the cheapest step might outweigh the costs for evaluating the original, unoptimized query expression.

In the scope of this work, two practical runtime optimizations have been picked out to demonstrate that simple runtime tests, backed by appropriate extensions of the evalu-ation framework, can have a strong impact on the execution time. Some benchmark results are listed in 4.2.1.

3.3.3.1 Direct Sequence Access

XQuery allows users to write code that looks similar to procedural code:

declare variable $data := doc(’input.xml’)//data for $i in 1 to count($data)

return $data[$i]

The query example generates a sequence ofdataelements, and the subsequent FLWOR expressions returns all items of the sequence11. While the bracket notation looks like a classical array offset, it is in fact a predicate containing a positional test – which is a special case in XQuery, as a predicate may contain arbitrary expressions. Hence, if the query is evaluated in a straightforward manner, and ifdatahasnelements, the predicate will be matched against all items of the sequencentimes, resulting inΘ(n2) (both best and worst case).

11Obviously, the variable reference$datawould yield the same result as the FLWOR expression.

3.3. Optimizations

As a positional predicate will never yield more than one result, a first optimization con-sists in adding a skip mechanism to the predicate test, which will be triggered at runtime after the first positional test has been successful. This will reduce costs to an average ofO(n22). In practice, however, this optimization yields much better costs as results are often limited to the first m elements of a sequence, in which case the costs are further lowered toO(m22).

If all items of a sequence are available at runtime, the data structure storing the sequence can be extended by methods to directly access items by their offset. While this concept does not harmonize with iterative, pipelined query evaluation (see 3.4.1), it perfectly goes hand in hand with pre-evaluated sequences, such as found in global variables, or in parts of the query which are repeatedly evaluated and thus cached, and the resulting costs will beO(n).

To decide if positional access is possible in a filter expression, such as the one shown in the example and described in the Summary (3.5), the predicate is evaluated at runtime.

If it yields a single positive integer – which might as well be true for a double number without fractional digits, a case which cannot be covered with static typing – and if the value of the main expression has already been evaluated and is fully available for posi-tional access, i.e., if the sequence contains a method for direct access, the relevant item is requested and returned as single result. If no direct access is available, a new iterator is created, which will be aborted as soon as the first result has been returned. If the predicate is not numeric, or if the predicate expression depends on the current context, or if more than one predicate has been defined for the filter expression, the standard evaluation path is taken, which supports both positional and boolean predicates.

3.3.3.2 General Comparisons

General comparisons are one of the most frequently used expressions in XQuery; ac-cordingly, small optimizations can lead to significant performance boosts. In the spec-ification, they are defined as “existentially quantified comparisons”, which atomize the items of both its operands and return true if one single comparison is successful. As a result, comparisons with empty sequences, such as() = () or() != (), return false whereas (1,2) > (2,1)returns true. As a general comparison withm∗nitems will have a worst case of O(m∗n), one of the item sequences needs to be iterated several times, and better performance can be expected if the intermediate results are cached.

Caching may be suboptimal, however, if only single items are to be compared (which

3.4. Evaluation are the types of comparisons that occur most often in practice). Depending on the cardi-nality of the operands, the best evaluation variant can be chosen step by step at runtime (note that details on iterative processing can be found in the upcoming Section 3.4.1):

1. If static typing indicates that both operands will yield 0 or 1 results, the expression can be optimized at compile time: the atomic values from both operands can be directly compared without further cardinality checks.

2. Otherwise, an iterator iter1 is requested for the first operand. If iter1 indicates that 0 results are expected,falseis returned.

3. If iter1 indicates 1 result, and if static typing indicates that the second operand will return 0 or 1 results, the first item ofiter1will be compared with the atomic value of the second operand. Otherwise, the second iterator iter2 is requested, andfalseis returned if the iterator will return 0 results.

4. If both iterators indicate 1 result, the firsts items that are returned by the iterators are compared.

5. If the number of results for iter1is unknown, and ifiter2will return 1 result, all items fromiter1are compared with the first item fromiter2.

6. If none of the cases applies, all results fromiter1are compared with the first item from iter2 and cached for repeated access. If no comparison was successful, the cached items are compared with all other items fromiter2.

While each of the listed conditions, except for the first, adds some computation overhead at runtime, it does not outbalance the time needed for caching or reiterating all items of an iterator.

3.4 Evaluation

After all normalizations and optimizations have been performed, the resulting expres-sion tree contains all information needed to evaluate the query. In the evaluation step, all data will be handled that cannot be statically processed. As was shown in the pre-vious section on query optimization, this step can be reduced to simply returning the result of a compiled query – which is often the case if the query is supposed to return statistical information on a database, using the fn:count()function, or if a query does not use database references at all.

3.4. Evaluation

Note that classical teaching on compiler construction allows for an additional code gen-eration step, which transforms an expression tree into executable code. This step is mandatory if the compiled code is materialized and executed later. In the context of XQuery, it has been applied to Qexo [Bot04], BEA/XQRL [FHK+03] and the Pathfinder [GMR+07]. Next, the commercial version of SAXONincludes an option to generate Java byte code, which promises an average speedup of 25% [Kay08]. In the scope of this work, code generation is skipped. Instead, the generated expression tree is always eval-uated directly after it has been compiled and optimized.