143 lines
5.4 KiB
Markdown
143 lines
5.4 KiB
Markdown
---
|
|
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
|
author: Michael Abrash
|
|
date: '1997-07-01'
|
|
isbn: '1576101746'
|
|
publisher: The Coriolis Group
|
|
category: 'Web and Software Development: Game Development,Web and Software Development:
|
|
Graphics and Multimedia Development'
|
|
chapter: '59'
|
|
pages: 1110-1112
|
|
---
|
|
|
|
Take a few minutes to look over Listing 59.4 and relate it to Listing
|
|
59.2. The structure is different, but upon examination it becomes clear
|
|
that both listings reflect the same underlying model: For each node,
|
|
visit the left subtree, visit the node, visit the right subtree. And
|
|
although Listing 59.4 is longer, that's mostly because I commented it
|
|
heavily to make sure its workings are understood; there are only 13
|
|
lines that actually do anything in Listing 59.4.
|
|
|
|
Let's look at it another way. All the code in Listing 59.2 does is say:
|
|
"Here I am at a node. First I'll visit the left subtree if there is one,
|
|
then I'll visit this node, then I'll visit the right subtree if there is
|
|
one. While I'm visiting the left subtree, I'll just push a marker on a
|
|
stack that tells me to come back here when the left subtree is done. If,
|
|
after visiting a node, there are no right children to visit and nothing
|
|
left on the stack, I'm finished. The code does this at each node—and
|
|
that's *all* it does. That's all Listing 59.4 does, too, but people tend
|
|
to get tangled up in pushes and pops and `while` loops when they use
|
|
data recursion. When the implementation model changes to one with which
|
|
they are unfamiliar, they abandon the perfectly good model they used
|
|
before and try to rederive it in the new context by the seat of their
|
|
pants.
|
|
|
|
> 
|
|
> Here's a secret when you're faced with a situation like this: Step back
|
|
> and get a clear picture of what your code has to do. Omit no steps. You
|
|
> should build a model that is so consistent and solid that you can
|
|
> instantly answer any question about how the code should behave in any
|
|
> situation. For example, my interviewees often decide, by trial and
|
|
> error, that there are two distinct types of right children: Right
|
|
> children visited after popping back to visit a node after the left
|
|
> subtree has been visited, and right children visited after descending to
|
|
> a node that has no left child. This makes the traversal code a mass of
|
|
> special cases, each of which has to be detected by the programmer by
|
|
> trying out scenarios. Worse, you can never be sure with this approach
|
|
> that you've caught all the special cases.
|
|
>
|
|
> The alternative is to develop and apply a unifying model. There aren't
|
|
> really two types of right children; the rule is that all right children
|
|
> are visited after their parents are visited, period. The presence or
|
|
> absence of a left child is irrelevant. The possibility that a right
|
|
> child may be reached via different code paths depending on the presence
|
|
> of a left child does not affect the overall model. While this
|
|
> distinction may seem trivial it is in fact crucial, because if you have
|
|
> the model down cold, you can always tell if the implementation is
|
|
> correct by comparing it with the model.
|
|
|
|
#### Measure and Learn {#Heading10}
|
|
|
|
How much difference does all this fuss make, anyway? Listing 59.5 is a
|
|
sample program that builds a tree, then calls `WalkTree` () to walk it
|
|
1,000 times, and times how long this takes. Using 32-bit Visual C++ 1.10
|
|
running on Windows NT, with default optimization selected, Listing 59.5
|
|
reports that Listing 59.4 is about 20 percent faster than Listing 59.2
|
|
on a 486/33, a reasonable return for a little code rearrangement,
|
|
especially when you consider that the speedup is diluted by calling the
|
|
`Visit()` function and by the cache miss that happens on virtually
|
|
every node access. (Listing 59.5 builds a rather unique tree, one in
|
|
which every node has exactly two children. Different sorts of trees can
|
|
and do produce different performance results. Always know what you're
|
|
measuring!)
|
|
|
|
**Listing 59.5 L59\_5.C**
|
|
|
|
```cpp
|
|
// Sample program to exercise and time the performance of
|
|
// implementations of WalkTree().
|
|
// Tested with 32-bit Visual C++ 1.10 under Windows NT.
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include "tree.h"
|
|
long VisitCount = 0;
|
|
void main(void);
|
|
void BuildTree(NODE *pNode, int RemainingDepth);
|
|
extern void WalkTree(NODE *pRootNode);
|
|
void main()
|
|
{
|
|
NODE RootNode;
|
|
int i;
|
|
long StartTime;
|
|
// Build a sample tree
|
|
BuildTree(&RootNode, 14);
|
|
// Walk the tree 1000 times and see how long it takes
|
|
StartTime = time(NULL);
|
|
for (i=0; i<1000; i++)
|
|
{
|
|
WalkTree(&RootNode);
|
|
}
|
|
printf("Seconds elapsed: %ld\n",
|
|
time(NULL) - StartTime);
|
|
getch();
|
|
}
|
|
//
|
|
// Function to add right and left subtrees of the
|
|
// specified depth off the passed-in node.
|
|
//
|
|
void BuildTree(NODE *pNode, int RemainingDepth)
|
|
{
|
|
if (RemainingDepth == 0)
|
|
{
|
|
pNode->pLeftChild = NULL;
|
|
pNode->pRightChild = NULL;
|
|
}
|
|
else
|
|
{
|
|
pNode->pLeftChild = malloc(sizeof(NODE));
|
|
if (pNode->pLeftChild == NULL)
|
|
{
|
|
printf("Out of memory\n");
|
|
exit(1);
|
|
}
|
|
pNode->pRightChild = malloc(sizeof(NODE));
|
|
if (pNode->pRightChild == NULL)
|
|
{
|
|
printf("Out of memory\n");
|
|
exit(1);
|
|
}
|
|
BuildTree(pNode->pLeftChild, RemainingDepth - 1);
|
|
BuildTree(pNode->pRightChild, RemainingDepth - 1);
|
|
}
|
|
}
|
|
//
|
|
// Node-visiting function so WalkTree() has something to
|
|
// call.
|
|
//
|
|
void Visit(NODE *pNode)
|
|
{
|
|
VisitCount++;
|
|
}
|
|
```
|