CS360 Final -- December 11, 1999. Question 3: Answer and grading

15 points

The Answer

Part 1: Hank's code is hard to read, but what it does is count the number of elements in the list. This is the return value of dlls(). It calls rec_call(), setting Tmp to the first element of the list, and rec_call() keeps calling itself while Tmp is not equal to the sentinel node. When Tmp equals the sentinel node, rec_call() calls longjmp which will cause the setjmp() call in dlls() to return with a value of 1. Dlls() will thus return CS, which is the number of elements in the list.

The easy way to rewrite dlls() is:

int dlls(Dllist d)
{
  Dllist tmp;
  int cs;

  cs = 0; 
  dll_traverse(tmp, d) cs++;
  return cs;
}

Part 2: The code in this part is a typical recursive list traversal. It is indeed inefficient. Hank's code is more efficient in two ways. First, since it uses global variables, it does not push the three arguments onto the stack at each recursive call. Therefore, it saves memory. Does it save processing time too? Well, certainly the pushing of d on the stack is wasteful. And popping the arguments off the stack is wasteful. I'm not sure that the other two pushes of arguments are though, unless the compiler was storing cs and tmp in registers, or unless you see effects of cache misses since the stack keeps growing. Still I would accept the fact that the global variables keep you from pushing arguments as an example of the efficiency of Hank's code.

The second efficiency of Hank's code over his predecessor's is that when Hank's reaches the end of the list, it calls longjmp, meaning that dlls() returns a few instructions later. In contrast, his predecessor's code has to call return once for each element in the list.

An additional improvement is that with a really large list, Hank's code will not run out of stack space as quickly as his predecessors.

Part 3: They are all O(n), where n is the number of elements on the list.

Part 4: The code above uses a simple for loop instead of the recursion. Therefore it uses no extra stack space, and you don't have to use the longjmp() trick to return quickly. It will be more efficient because it uses less stack space, and because it does not have to push the stack pointer, frame pointer and pc onto the stack at each iteration.

Interestingly, a very smart compiler will be able to realize that in Hank's predecessor's rec_call, the recursive calls are all the last statements. These may be identified as examples of tail recursion, which can be optimized by overwriting their stacks rather than creating new stack frames. Not that you should care too much, but that is the argument that functional programming people make to say that their methodology can be made efficient.


Grading