• No results found

Profiling Approach and Recursion Analysis

2.3 Cost Models for Skeleton-Oriented Programming

2.3.7 Profiling Approach and Recursion Analysis

Apart from skeletal restrictions, our language VEC-BSP imposes additional restric-tions to achieve static cost predictability and automatability of cost analysis. For ex-ample, we exclude non-shapely functions like filter whose result shape depends on input data, and recursive functions, whose termination and cost are not decidable in the general case. As these restrictions are strict, our static cost analysis in this thesis could be incorporated with more experimental approaches for a more practical analysis system. Those approaches such as profiling methods and recursion analysis themselves have been long known as important research areas. The remainder of this chapter sum-marises some related works of these issues.

Profiling Approach

One approach for extracting information about the performance of program is to exe-cute it with some sample input and to generate profiling information. This information is then fed back into the program development or compilation process and can be used to generate more efficient code. Ideally, predicting the execution time solely by static analysis is preferable because a programmer or a compiler can make all decisions based on source code. In contrast, information of the profiling method depends on the choice of the initial input set. If the run-time behaviour of the program varies much between different inputs, to reach a good result without a large-compile time is difficult and, the choice of good sample input is not obvious in general. However, as the execution time of a program is not a decidable property and information of input data is desirable for accurate prediction in some case, for example at the branching points in the program, introducing a profiling approach would be indispensable in more practical use. Good examples of the profiling approach combined with a skeleton-based approach can be seen in the works of the Heriot-Watt group. In Busvine’s PUFF compiler [24], the pro-gram is run on one or more sets of data, collecting statistics about computation costs and execution frequencies. This information is used to transform the program into a parallel version that has improved performance. Bratvold’s SkelML [18] is a skeleton based parallelising compiler, which is based on sequential program instrumentation

through Structural Operational Semantics (SOS) [64]. Skeleton performance models are instantiated using the SOS measures to determine useful parallelism. Michaelson et al. [61] present the design of an architecture-independent parallelising compiler for SML in which these costs are parameterised over machine specific parameters, so that instantiating these parameters and combining the profiling information can give accurate granularity information.

Recursion Analysis

Functions are said to be defined recursively when the body of the definition refers to the function itself. We usually demand that recursive definitions are terminating, i.e. given some particular input the function will call itself only a finite number of times before stopping with some output. In general, however, there is no guarantee that a function defined by recursion will always terminate. The usual approach is to provide the user with a pre-defined set of well-founded induction schemes. To use a scheme not specified in this set, the user must specify an ordering and prove that this orderings is well-founded. There are possible constraints on recursion to aid analysis of termination. The simplest way to ensure termination is to forbid recursion. This would give a restrictive language. Another alternative is to restrict the recursive function to be primitive recursive, as all primitive recursive programs terminate with easily characterisable time and space behaviour. There are functions that are not primitive recursive to which we cannot in any simple way give an upper bound for the number of reductions needed when applying it to an argument.

Burstall [22] contributed structured recursion, a generalised form of primitive recur-sion, to analytic syntax, with an associated principle of structural induction. Burstall [23] also showed that if the recursion is combined with a case expression which de-composes elements of the data type, the ordinary scoping rule for variables can be used to ensure termination, without imposing any special schema.

Abel [1] has introduced a language based upon lambda calculus with products, coprod-ucts and strictly positive inductive types that allows the definition of recursive terms.

Their termination checkerfoetusensures that all such terms are structurally recursive,

i.e. recursive calls appear only with arguments structurally smaller than the input pa-rameters of terms considered.

Walter [84] has described reduction checking, which is sometimes referred to as Walter recursion. His estimation calculus examines whether functions are terminating and also whether the output of the function is smaller than the input. This information can be used to check termination of nested recursive functions.

More work on the estimation calculus has been done by Bundy and others. Recursion editor [20] is an editor for Prolog that only allows terminating definitions, which en-sure the termination of severely restricted kinds of recursive procedures chosen from Peter’s classification. More recently, in CYNTHIA [85], an editor for a subset of ML, which grew out of work on recursion editor, each ML function definition is represented as a proof of a specification of that function using the idea of proofs-as-programs [49].

The proof is written in Oyster [21], a proof-checker implementing a variant of Martin-L¨of Type Theory. CYNTHIA restricts the user to the set of Walter recursive functions, which includes primitive recursive functions over an inductively-defined data types, multiple recursive functions, nested recursive functions and functions that reference previously defined functions in a recursive call. It analyse the termination of the pro-gram and gives useful feedback.

Telford and Turner [80] are investigating Elementary Strong Functional programming, i.e., functional programming where only terminating functions can be defined. They use abstract interpretations to ensure termination. They can handle a wider class of functions than Walters recursion since they keep track not of whether an argument is decreasing but how much it is decreasing or increasing, thus allowing temporary growth that is compensated by sufficient shrinkage later.

Related works on deriving cost information (statically or experimentally) and the treat-ment of recursion include the following.

Busvine’s PUFF [24], which compiles SML to occam2, uses instrumentation to iden-tify useful parallelism in linear recursion.

The ACE system of Le M´etayer [60] transforms an FP program with call-by-name

se-mantics into a program with call-by-value sese-mantics. This performs a macro-analysis, that is, it measures the time in the number of applications of the dominant operation which is used in the program. He uses a set of rewrite rules to derive complexity functions, simplify them and finally eliminate recursion.

Huelsbergen et al. [50] were able to handle recursion successfully by using abstraction.

They have defined an abstract interpretation of a higher order, strict language for de-termining computation cost, which uses dynamic estimates of the sized data structure.

Their analysis uses the well-known trick of iteration in the abstract interpretation stops as soon as a certain bound for the computation costs of an expression is surpassed.

This prevents non-termination in the analysis.

Rosendahl [74] presents a program transformation that yields a time bounded program for a given first-order Lisp program. His system deals with recursive functions by providing a set of translation rules that eliminate recursion.

Loidl and Hammond [57] present an inference system to determine the cost of evaluat-ing expressions in a strict purely functional language. Upper bounds can be derived for both computation and size of data structures. The analysis is a synthesis of the sized system of Hughes et al. [51], and the time system of Dominic et al. [32], which was extended to static dependent costs by Reistad and Gifford [73]. Sized types can also be used to analyse the costs of user-defined recursive functions.