• No results found

2.2 Automatic Amortized Analysis

2.2.1 Linear Potential

The first automatic amortized analysis was introduced by Hofmann and Jost [HJ03] to analyze the heap-space consumption of first-order functional programs. They fixed potential functions to belinearin the size of the data in the memory.

The potential at a program point is defined by a static annotation of the reachable data at that point. More precisely, inductive data structures are statically annotated with non-negative rational numbersqto define non-negative potentialsΦ(n)=q·n

as a function of the sizenof the data. Then a sound, albeit incomplete, type-based analysis of the program text statically verifies that the potential is sufficient to pay for all operations that are performed on this data structure during any possible evaluation of the program.

This idea is best explained by example. Consider the functionattachthat takes an integer and a list of integers and returns a list of pairs of integers such that the

first argument is attached to every element of the list. For instance, the expression

attach(1,[1,2,3,4])evaluates to[(1,1),(1,2),(1,3),(1,4)]. The function can be implemented

as follows.

attach(x,l) = match l with | nil → nil

| y::ys → (x,y)::(attach (x,ys))

Suppose that we need three memory cells to create a list cell of the resulting list—two cells for the pair of integers and one cell for the pointer to the next list element. The heap-space usage of an execution ofattach(x,`)is then 3nmemory cells ifnis the length of`.

To infer an upper bound on the heap-space usage of the function, we annotate the type ofattachwith a priori unknown resource annotationss,s0,qandpthat range over non-negative rational numbers.

attach : (int,Lq(int))−−−→s/s0 Lp(int,int)

The intuitive meaning of the resulting typing is as follows: to evaluateattach(x,`)one needsqmemory cells per element in the list`andsadditional memory cells. After the evaluation there ares0 memory cells andpcells per element of the returned list left. We say that the list`has potentialΦ(`,q)=q· |`|and that the list`0=attach(x,`)has potentialΦ(`0,p)=p· |`0|.

A static type analysis of the program code then derives linear constraints on the resource annotations. In the case ofattach, the constraints would essentially state that

q≥3+pandss0. Every valid instantiation of the resource annotations must satisfy these constraints. For instance, the following typing ofattachis valid.

attach : (int,L(3)(int))−−−→0/0 L(0)(int,int)

It states that the heap-space consumption of the function is less than the initial potential 3·nifnis the length of the input list and thus furnishes a tight upper bound. The function

attachcan also be typed as follows.

attach : (int,L(5)(int))−−−→6/6 L(2)(int,int)

This typing could be used for an inner occurrence ofattachto type an expression like

f(attach(z,ys))if the evaluation off(`) would consume 6+2· |`|heap cells.

The use of linear potential functions relieves one of the burden of having to manipu- late symbolic expressions during the analysis by a priori fixing their format. This gives rise to a particularly efficient inference algorithm for the type annotations. It works like a standard type inference in which simple linear constraints are collected as each type rule is applied.

The constraints are solved with a linear-programming solver (LP solver) to obtain the best possible typing for the program. The function type that is needed to minimize the initial potential depends on the context in which the function is applied.

Automatic amortized analysis can be used with generic resource metrics [JLH+09]. As a result it can derive bounds on every quantity whose consumption in an atomic step is bounded by a constant. An important example istime consumption. Consider for instance the functionfilter:(int,L(int))→L(int) that removes the multiples of a given integer from a list of integers.

filter(p,l) = match l with | nil → nil

| x::xs → let xs’ = filter(p,xs) in

if x mod p == 0 then xs’ else x::xs’

Suppose that the evaluation of the expressionfilter(p,`)takes at most 16· |`| +3 atomic steps. Then the following typing expresses a tight upper bound.

filter : (int,L(16)(int))−−−→3/0 L(0)(int)

As in the case of heap-space consumption, we can infer these potential annotations by solving the linear constraints that are produced by our type inference algorithm.

Since amortized analysis takes into account the interaction between the steps of a computation, it obtains tighter bounds than a mere addition of the worst case resource bounds of the individual steps. Generally, the constants in the bounds are very precise and often match exactly the worst-case behavior of the functions. Thanks to efficient, off-the-shelf LP solvers, the analysis takes only a few seconds, even on larger programs. Hofmann and Jost’s technique has been successfully applied to object-oriented programs [HJ06, HR09], to generic resource metrics [JLH+09, Cam09], to polymorphic and higher-order programs [JHLH10], and to Java-like bytecode by means of separation logic [Atk10]. The main limitation shared by these analysis systems is theirrestriction to linear resource boundsto enable efficient inference using linear constraint solving.

Chapter 4 formally describes linear automatic amortized analysis for first-order monomorphic functional programs.