WCET Analysis - Fibonacci recursion, structure matters

The natural way

Here is a recursion function, generating Fibonacci number.

int fib (int z) {
    int r;
    if (z == 0)
        r = 0;
    else if (z == 1)
        r = 1;
    else
        r = fib(z-1) + fib(z-2);
    return r;
}

The first two items of the Fib are 0 and 1. So it is very nature to write the code like this. But it is under-optimized, in a "Fibonacci-way".

WCET Analysis

Now we do the WCET(worst case execution time) analysis, assuming:

  • Each declaration or assignment statement costs 1 time unit
  • Each compare statement costs 1 time unit
  • Each return statement costs 1 time unit
  • Each addition or subtraction operation costs 4 time units.
  • A function call costs 2 time units plus WCET for the function in question.
  • All other language constructs can be assumed to take 0 time units to execute.

Let's make $f(z)$ to be the WCET of the fib(z), so $f(0)$ is the WCET of the fib(0) function execution. For different z value, the code being executed is different, with different length, and different WCET.

Now divide the code snippet to 3 parts, and analyze them seperately:

// path a (z=0): 4
int fib (int z) {
    int r; // declaration (1)
    if (z == 0) // compare (1)
        r = 0; // assignment (1)
    return r; // return (1)
}

For path a, the WCET of fib(0) is 4, that is $f(0)=4$

// path b (z=1): 5
int fib (int z) {
    int r; // declaration (1)
    if (z == 0) // compare (1)
    	;
    else if (z == 1) // compare (1)
    	;
    else
        r = 1; // assignment (1)
    return r; // return (1)
}

For path b, $f(1)=5$ because we have one more comparison executed with the if-else statement.

// path c (z>=2): 21+f(z-1)+f(z-2)
int fib (int z) {
    int r; // declaration (1)
    if (z == 0) // compare (1)
        ;
    else if (z == 1) // compare (1)
        ;
    else
        r = fib(z-1) + fib(z-2); // 3*4+2*2+f(z-1)+f(z-2)
    return r; // return (1)
}

For r = fib(z-1) + fib(z-2); it takes  3 ALUs(add/sub), 2 function calls, 1 assignment, and it contains the code of other 2 fib calling. So $f(z)=21+f(z-1)+f(z-2)$ for path c. And you may noticed that, we need to do one more comparison, same as path b.

For a if-else code snippet, the structure/placement matters, especially it will be called recursively.

if (cond1)
    func1(); // execute after cond1
else if (cond2)
    func1(); // execute after cond1, cond2
else if (cond3)
    func1(); // execute after cond1, cond2, cond3
else
    func1(); // execute after cond1, cond2, cond3

Let me use some real numbers, if we want to compute the WCET for fib(5), we'll have:

$$
\begin{align*}
f(2)=f(1)+f(0)+c=a+b+c \\
f(3)=f(2)+f(1)+c=a+2b+2c \\
f(4)=f(3)+f(2)+c=2a+3b+4c \\
f(5)=f(4)+f(3)+c=3a+5b+7c \\
\end{align*}
$$

The WCET of fib(5) is composed of some path a, more path b, much more path c. And try to look at it vertically, you may understand why I said, this is under-optimized in a Fibonacci-way.

The weight of path A grows in a Fibonacci-way; the weight of path B grows in a same fashion but one step ahead (one step in Fibonacci...). And for path C? Also grows in a Fibonacci fashion, but every time 1 is added to the weight (for the execution of path c itself).

For this natural version code, the WCET of fib(5) is $3\times 4+5\times 5+7\times 21=184$

Better Structure, nature way for the machine

int fib (int z) {
    int r;
    if (z > 1)
        r = fib(z-1) + fib(z-2);
    else if (z == 1)
        r = 1;
    else
        r = 0;
    return r;
}

If we switch the position of path C with path A, it will save one comparison for path C and add one to path A, which makes WCET of the new fib function is $3\times 5 + 5\times 5 + 7\times 20=180$. The difference is 4 time units of comparison.

For a computation of  fib(10), the difference will be 54 time units.

For a computation of fib(20), the difference will be 6764 time units...