3.1. Análisis de datos
3.1.1. Entrevista No 1 Gerente General
Another form of optimization is possible in the cases above where we used double recur- sion. For example, let us analyze the work done in calculating a Fiboncci number using the straightforward recursive code asked for in Project 7. Unlike the factorial example which is somewhat unnatural because a simple loop program is faster and easier, we will consider one of the most famous naturally recursive functions. Introduced around 1202 by Fibonacci, they arose first in calculating how many rabbits there would be in succeeding generations under a simple rule of birth. These same numbers appear in many applications. Their mathematical definition (in one simple form) is:
Fib(0) = 0 Fib(1) = 1
Fib(n) = Fib(n-1) + Fib(n-2) for n >=2
In C this is immediately:
int Fib(int n) {
if (n == 0 || n == 1) return n; return Fib(n-1) + Fib(n-2); }
Following the mathematical description,F ib(10) =F ib(9)+F ib(8) = (F ib(8)+F ib(7))+ (F ib(7)+F ib(6)) =. . .forn = 10. We see that some values will be calculated repeatedly. In fact, the Fibonacci numbers grow in size exponentially withnand so does the number of recursive calls.
A very simple solution to this problem is to keep a table of those values that have already been calculated and so evaluate each Fibonacci number only once. The table lookup method is called Dynamic Programming and proceeds from small cases to larger.
Let us apply this technique to the Fibonacci numbers. We will use an array that can hold 50 integers since for larger n the corresponding number will overflow anyhow. Since the ARM initializes space to the value zero, we will not put in any code to do that initialization but it might be necessary in other systems or languages. Another concession will be to use zero as an indication that the value has not been calculated yet. That forces us to note that F ib(2) = 1 and use that in addition to the usual base cases since F ib(0) = 0 would not look like a previously calculated value.
@ Fib.s @
@ Dynamic Programming implementation @@@@@@@ INPUT: @@@@@@@@
11.7. Dynamic Programming
@@ non-negative integer n in r0
@@ (no test: negative values address locations outside array) @ @@@@@@@ OUTPUT: @@@@@@@@@@@ @ @@ nth Fibonacci number in r0 @ @@@@@@@ TO USE: @@@@@@@@@@@ @
@@ mov r0, n @ Put the n in r0
@@ bl Fib @ Returns with answer in r0 @
Fib: @ <= entry point
@ save registers
push {r4, r5, r6, lr} @ 8 byte aligned @ check for zero
cmp r0, #0
beq return @ Fib(0) = 0 @ check FibArray for answer
mov r4, r0 @ save n in r4
mov r1, r0, LSL #2 @ take r0 = n and get r1 = 4*n ldr r0, =FibArray @ get array address
add r0, r1, r0 @ actual location address
mov r6, r0 @ save that location address in r6 ldr r0, [r0] @ what’s there
cmp r0, #0 @ has it been calculated yet? bne return @ if not zero, found it already @ otherwise we have to actually do the calculation @ adjust input parameter
sub r0, r4, #1 @ n-1 new argument bl Fib @ first recursive call mov r5, r0 @ save result in r5
sub r0, r4, #2 @ now use n-2 as argument bl Fib @ second recursive call add r0, r5, r0 @ new result
str r0, [r6] @ save it in FibArray at correct location @ restore registers return: pop {r4, r5, r6, lr} bx lr .data FibArray: .word 0 @ Fib(0) = 0 .word 1 @ Fib(1) = 1
11. Recursion and the Stack
.word 1 @ Fib(2) = 1
.space 188 @ room for 47 more
Even on a fairly fast processor we can finally do so large a calculation that the time lag will be quite noticeable. The straight recursive Fib function will take a significant amount of time to run when given, for example, an n value of 40. With most of the recursive calls removed by Dynamic Programming, it should be instantaneous.
Projects
1. When using themulinstruction, we wrotemul r0, r0, r1. Compare that tomul r0, r1, r0.
2. It is frequently needed to find out what is the information at the top of the stack. Write a simple function top that returns that value but does not (permanently) change the stack, using only push and pop. This shows that a built-in function
top is not necessary but it might be available.
3. Modify the factorial function code to check for input greater than 12. What happens with larger values? Why?
4. Write and test a program to reverse an input list of numbers using recursion. 5. Write and test a tail-recursive function to calculate the product of two integers
product(n, m) = n ∗m using only addition and subtraction as if there were no multiply hardware and it must be done in software.
6. Write and test a tail-recursive function to calculate the sum of two integers sum(n, m) =n+m using onlyincrement(adding 1) anddecrement(subtracting 1) as if there were no adding hardware and it must be done in software using only increment and decrement hardware.
7. (Fibonacci Numbers) Write a recursive (but not tail-recursive) assembler language program to calculate the Fibonacci number of a non-negative input integer by direct translation of the C code. How large an integer can your program handle? How slow is it?
8. (Binomial Coefficients) The number of combinations ofn identical objects takenk at a time, denoted by C(n, k), can be calculated using the recursive formula given in C/C++/Java code by
public int C(int n, int k) {
if (n == 0 || k == 0 || k == n) return 1; return C(n-1,k) + C(n-1,k-1); // 0 < k < n }
11.7. Dynamic Programming
In our study of Discrete Mathematics we will see thatC(n, k) is thekth coefficient in the expansion of the expression (x+y)n. These numbers are called thebinomial coefficients. Again it is easy to translate this directly into ARM assembly code. Write a program that prompts for n and k and returns C(n, k). Add checks for incorrect input in your code for C(n, k); return −1 if 0≤k ≤n fails in any way. 9. Using the function C(n, k) as a model, write assembler code to calculate the
recursive function J defined byJ(0) = 0, J(1) = 1, and J(n) = 2nJ(n−1)−J(n−2)
for n≥2. Assume, as usual, thatr0 contains n and the answer is returned in r0. Write a driver program to print out the values ofJ(n) forn = 0 ton = 25. (Also, assume the values are small integers.)
10. Fix the tail-recursive version of factorial so that registers r0 and r1 are not changed.
11. Write a “driver” program to call on our tail-recursive Fib.s program and print out values.
12 Conditional Execution
Several times, in earlier chapters, we stated that the ARM architecture was designed with the embedded world in mind. Although the cost of the memory becomes lower everyday, it still may account for an important part of the budget of an embedded system. The ARM instruction set has several features meant to reduce the impact of code size and, as we shall see, branching. One of those features is predication.
12.1
Predication
We saw in earlier chapters how to use branches in our program in order to modify the execution flow of instructions and implement useful control structures. Branches can be unconditional, for instance when calling a function, or conditional when we want to jump to some part of the code only when a previously tested condition is met.
Predication is related to conditional branches. What if, instead of branching to some part of our code meant to be executed only when a condition C holds, we were able to turn off some instructions when the C condition does not hold? Consider some case like this:
if (C) T(); else
F();
Using predication (and with some invented syntax to express it) we could write the above if-else statement as follows:
P = C; @ get the predicate
[P] T(); @ if P is true, do T(), otherwise, do nothing [!P] F(); @ if P is false, do F(), otherwise, do nothing
This way we avoid branches. But, why would be want to avoid branches? Well, exe- cuting a conditional branch involves a bit of uncertainty as to what will happen in the future. This deserves a bit of explanation.
12. Conditional Execution