Skip to main content
GameDev.net gamedev.net
🔒 Locked

How do you walk a BSP without recursion?..

Started by monkeyman Dec 1, 2000 at 12:38 PM 13 replies 9.4k views
Original Post
monkeyman
monkeyman
I''ve already got the BSP thing going, but how do you walk through it without recursion?..I''m sure I could design some type of nightmare, but I''d rather hear it from someone who''s done it..anyone?.. "Like all good things, it starts with a monkey.."
"Like all good things, it starts with a monkey.."
felisandria
felisandria
Um... why would you want to?

I''m assuming you''re trying to display your tree or something. I guess you could do it by throwing everything you find into a linked list (or an array if you have a finite known number of nodes) but quite honestly walking through a binary tree without recursion is going to be what I would term "really freaking painful".

-fel
~ The opinions stated by this individual are the opinions of this individual and not the opinions of her company, any organization she might be part of, her parrot, or anyone else. ~
SiCrane
SiCrane
In order to program any binary tree traversal without explicit recursion (not just a BSP), you need to manually maintain a stack. One way is to maintain a resizable array of pointers to nodes. Then for node traversal, keep going down the one side of the nodes, and whenever you transition off that side of a node, put the node at the end of the array. When you reach a node that you can no longer traverse from on that side, get the last node in the array, remove it, and process the node on that node''s other side, again traversing down the original direction.
monkeyman
monkeyman
I was afraid of that..here''s a quote from an inspiring SoapBox post:

////////////////////////////
Want to know why we get so many annoying newbie questions? Because the newbies do not have any foundation of knowledge to even attempt to create a game as simple as Pong. There are questions about DirectX from people who can''t even understand the object model exposed by C++ and/or COM. Posts about optimization problems from those who can''t even code a non-recursive in-order traversal of a binary tree.
////////////////////////////
Posted By: msn@glue.umd.edu

My engine runs just fine, but if there''s an easy, efficient method of non-recursively walking the BSP that gives a big performance boost, naturally I want to know about it
BTW I am No Newbie..
I''m lead engine programmer with a top-notch software R&D team, and I''ve never SEEN an efficient BSP-based engine that DIDN''T use recursion..obviously I''ve been in the dark about things

It''s a real shame that msn@glue.umd.edu sounds like such a dick, otherwise I''d probably ask him directly..



"Like all good things, it starts with a monkey.."
"Like all good things, it starts with a monkey.."
felisandria
felisandria
To be honest I kind of thought the whole point of using recursion is that if you can manage to get it working it''s the fastest, most elegant, and shortest (code-length-wise) way of doing whatever it is you''re trying to do.

I''d be curious too if someone could come up with a traversal of a binary tree not using recursion that somehow buys you something. Can''t really imagine what it would buy you to be honest, since it would probably have to be rather long, and you''re going to have to visit every node regardless so it wouldn''t speed it up any probably. It probably couldn''t be more efficient, because it''s really efficient to do recursion on BSP trees due to the fact that you''re actually technically queueing the information you''re reading but you''re queueing it on the stack itself in the form of information held in temporarily paused functions, and I can''t imagine coming up with anything more efficient than that.

-fel
~ The opinions stated by this individual are the opinions of this individual and not the opinions of her company, any organization she might be part of, her parrot, or anyone else. ~
Stoffel
Stoffel
We had a fairly lengthy discussion of this in a thread about a month back. I was strongly fighting for the side of recursion. Well, a lot of code flew around, and it turns out that for a lot of recursive problems, the non-recursive solution executes faster. Calls to recursive functions do take some time. I think if one were to implement an inline stack, one could probably get some performance gain. It would have to be profiled experimentally, but that was the outcome of the discussion. I was pretty disappointed, since recursion is so elegant & nifty. Oh well.
msn12b
msn12b
quote:
Original post by monkeyman
It's a real shame that msn@glue.umd.edu sounds like such a dick, otherwise I'd probably ask him directly..



That email doesn't work anymore... U of MD deleted my account when I graduated .

Anywho, doing a non-recursive inorder traversal of a BSP tree with full optimizations turned on in VC6 will not run noticeably faster than a regular recursive traversal. The VC compiler has some impressive optimization algorithms, and will optimize the tail recursion into a simple loop.[1]

But, for the sake of completeness, here's some pseudocode:

    

struct Node
{
Node* left;
Node* right;
T data;
};

void WalkInorder( Node* root )
{
Node* stack[]; //allocate this however you want.

Node* iter = stack;
*iter++ = 0;

Node* curr = root;

while( curr )
{
while( curr )
{
*iter++ = curr;
curr = curr->left;
}

if( *--iter )
{
ProcessNode( *iter );

curr = *iter->right;
}
}
};



Note that for a BSP, you'll need to muck around so that you traverse down the correct side. That's left up as an exercise to the reader however

MSN

[1] Zen of Graphics Programming, 2nd edition. Check here for a possibly better implementation of a non-recursive inorder tree traversal.


Edited by - msn12b on December 1, 2000 7:17:32 PM
monkeyman
monkeyman
Gee, you don''t sound like such a dick after all

The pseudocode definitely helped, I see what you''re saying
now..I have never seen a better example of putting pointer
arithmetic to work..I''ll have to try that..

I know recursion can be an evil thing in high-performance apps, but I hadn''t seen an alternative to the BSP walk until now..I hadn''t considered the pointer math..now I shall take over the world >

Thanks for answering my post despite my comments

You are the man

"Like all good things, it starts with a monkey.."
"Like all good things, it starts with a monkey.."
SiCrane
SiCrane
quote:
Original post by felisandria
To be honest I kind of thought the whole point of using recursion is that if you can manage to get it working it''s the fastest, most elegant, and shortest (code-length-wise) way of doing whatever it is you''re trying to do.


Provided, of course, that the algorithm is particularly amenable to recursive solutions. Consider, for example, that tic-tac-toe problem that just came up.
quote:

I''d be curious too if someone could come up with a traversal of a binary tree not using recursion that somehow buys you something. Can''t really imagine what it would buy you to be honest, since it would probably have to be rather long, and you''re going to have to visit every node regardless so it wouldn''t speed it up any probably. It probably couldn''t be more efficient, because it''s really efficient to do recursion on BSP trees due to the fact that you''re actually technically queueing the information you''re reading but you''re queueing it on the stack itself in the form of information held in temporarily paused functions, and I can''t imagine coming up with anything more efficient than that.

-fel

For argument''s sake, let''s consider the recursive implementation of a function independent binary tree traversal on a pipelined MIPS processor (the latter because I don''t want arguments on the relative merits of the x86 instruction cycle length, encoding, etc.)
It''s function prototype would probably looks something like:
void Traverse(Node * root, int (*fp)(Node *));

The register convention for the MIPS processor imply that the arguments are passed through the registers. So probably $a0 and $a1 will receive the node pointer and the function pointer. However because this is a recursive function we''ll need to store these values on the stack. Minimally you''d need, for every function call, to push the Node pointer, the function pointer, and the return address on the stack every time, for the recursive implementation. Thankfully, we can probably get away with not pushing the stack or frame pointers.

Now we''re going to do something vaguely evil and start constructing an iterative traversal of the tree using the stack as our stack. We''ll dive into assembly, but we still have to obey certain register conventions if we want this code to play happily with other code. (for example, that nice function that we got as an argument)

First things first. We notice that the function pointer doesn''t change for any of the invocations of the recursive call. So upon entry to our new function, we move the function pointer to $s0. And because we call a function from this function we also need to store the return address. Let''s stick it on the stack. We''ll place a sentinal value of zero at the current location of the stack pointer. Every time we encounter a new node, we decrement the stack pointer and place the node on the stack. And everytime can no longer left traverse, we pop a node from the stack, stopping when we hit our sentinal.

Assembly pseduo-code, for the recursive function:

$sp = $sp - 12 // stack stuff
$sp[12] = $s0
$sp[8] = $s1
$sp[4] = $ra
$s0 = $a0
$s1 = $a1
$t0 = $a0[0] // node->left
bez $t0, skip1 // if node->left traverse left
$a0 = $t0
jal SELF // recurse on left
skip1:
$a0 = $s0
jal $s1 // perform function
$a0 = $s0[4] // node->right
bez $a0, skip2 // if node->right traverse right
$a1 = $s1
jal SELF // recurse on right
skip2:
$ra = $sp[4]
$s0 = $sp[12]
$s1 = $so[8]
$sp = $sp + 12
jre $ra

Assembly pseudo-code, for the iterative function:

$sp = $sp - 16
$sp[16] = $s0 // store $s vars
$sp[12] = $s3
$sp[8] = $ra // store return address
$sp[4] = 0 // install sentinal
$s0 = $a1 // fp
$s3 = $a0 // node address
bez $s3, return
outer:
$sp[0] = $s3 // push current node on stack
$sp = $sp - 4
$s3 = $s3[0] // current node = node->left
bnez $s3, outer // repeat while not zero
inner:
$sp = $sp + 4 // pop node from stack
$s3 = $sp[0]
bez $s3, return // reached sentinal, so return
$a0 = $s3 // process node
jal $s0
$s3 = $s3[4] // current node = node->right
bnez $s3, outer
b inner
return:
$ra = $sp[4]
$s0 = $sp[12]
$s3 = $sp[8]
$sp = $sp - 12
jre $ra

It may be hard to see, but the memory motion in the latter case is much lower. Everytime the recursive function is called three items are placed on the stack, and on exit three items are popped from the stack. In the iterative function whenever the stack is accessed (aside from entry/exit) only one load/store is called. So it''s overall more cache friendly. And because we use the stack as our stack, no additional memory allocation overhead is incurred either. Also, when performing the actual traversals, fewer instructions are needed in the iterative method. i.e. the number of instructions in the loop is smaller than the number of instructions in the recursive funciton.
bishop_pass
bishop_pass
quote:
Original post by Stoffel

We had a fairly lengthy discussion of this in a thread about a month back. I was strongly fighting for the side of recursion. Well, a lot of code flew around, and it turns out that for a lot of recursive problems, the non-recursive solution executes faster. Calls to recursive functions do take some time. I think if one were to implement an inline stack, one could probably get some performance gain. It would have to be profiled experimentally, but that was the outcome of the discussion. I was pretty disappointed, since recursion is so elegant & nifty. Oh well.


Uhh, I believe the end result of the above mentioned discussion was that a truly native recursive function will save you maybe 6% or so if implemented iteratively, assuming there is no actual work to do on each node. If you actually have to do work at each node (welcome to the real world, people), then your savings drops to something possibly significantly less than 6%.





_______________________________
"To understand the horse you'll find that you're going to be working on yourself. The horse will give you the answers and he will question you to see if you are sure or not."
- Ray Hunt, in
Serge K
Serge K
Also recursion is bad for a branch-prediction.
All current CPU have a small return-address stack (usually 8..16 entries).

from AMD Athlon Processor x86 Code Optimization Guide :
quote:

Avoid recursive functions due to the danger of overflowing the return address stack. Convert end-recursive functions to iterative code.

When the 12 entry return-address stack gets out of
synchronization, the latency of returns increase. The return-address stack becomes out of sync when:
- calls and returns do not match
- the depth of the return-address stack is exceeded because
of too many levels of nested functions calls

Shannon Barber
Shannon Barber
Thread the BSP tree.
Use the Next pointer (threaded trees do !).

Good luck, dynamic tree threading is hard. I mean hard .
Look up Threaded AVL trees for some pointers...

no stacks, no recurrsion; just another 4bytes a node and a lot of programmer's grease.

Have none of you never heard of threaded trees!? Or am I on acid, and dreaming of something that doesn't exist?

...
quote:

In order to program any [unthreaded] binary tree traversal without explicit recursion (not just a BSP), you need to manually maintain a stack



...
I believe the end result of that recursion discussion ended (for me at least) with the empirical data of recursion costing you about 0.137Hz per node more than non-recursion.... don't remember the number exactly...
So that'd turn into 0.137 per node per frame for a bsp tranversals. And it's that per frame part that suddenly makes a difference. It's not huge, but might be a couple of %... not sure if I'd consider it worth it, but I'm curious to see the actually performance gain (or loss )
On 33MHz it could be a huge difference... but not so much on a 1GHz...


...
Hey monkeyman, using COMdomized code is a bit easier than creating COMdomized code! And when I was a newbie, I could have made pong! I made my own acsii qbasic Bomb! game :p


...
quote:

[MSCV optimizes] tail recursion into a simple loop.


Using the stack as a non-recursive stack implementation? or how? You mentioned this before, I'm just curious how it does it...


...
Doesn't a bsp's do most of its work by virtual of the transversal? ie little work to do when visiting a node, it's either in or out, next node?

Edited by - Magmai Kai Holmlor on December 5, 2000 2:11:24 AM
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
SiCrane
SiCrane
quote:
Original post by Magmai Kai Holmlor
Thread the BSP tree.
Use the Next pointer (threaded trees do !).

Good luck, dynamic tree threading is hard. I mean hard .
Look up Threaded AVL trees for some pointers...

no stacks, no recurrsion; just another 4bytes a node and a lot of programmer''s grease.

Have none of you never heard of threaded trees!? Or am I on acid, and dreaming of something that doesn''t exist?

...
In order to program any [unthreaded] binary tree traversal without explicit recursion (not just a BSP), you need to manually maintain a stack


If you want to get technical, then you might say that you are maintaining the stack by threading the tree.... After all by threading the tree you''re maintaining a linked list layer on top of the tree which stores traversal order....

And of course, in order to thread a non-threaded tree you have to do an inorder traversal anyway.

quote:

------------
[MSCV optimizes] tail recursion into a simple loop.
------------
Using the stack as a non-recursive stack implementation? or how? You mentioned this before, I''m just curious how it does it...


Given

class Node {
public:
Node * left;
Node * right;
int data;
};

void Traverse(Node * root) {
if (root == NULL) return;
Traverse(root->left);
printf("%d\n", root->data);
Traverse(root->right);
}

(Yes, it''s a brain-dead traversal implementation, but it''s the code that most clearly illustrates how the tail-recursion is handled.)
MSVC on /Ox will turn it into

_root$ = 8
?Traverse@@YAXPAVNode@@@Z PROC NEAR ; Traverse
; File node.cpp
; Line 10
push esi
; Line 11
mov esi, DWORD PTR _root$[esp]
test esi, esi
je SHORT $L551
$L548:
; Line 12
mov eax, DWORD PTR [esi]
push eax
call ?Traverse@@YAXPAVNode@@@Z ; Traverse
; Line 13
mov ecx, DWORD PTR [esi+8]
push ecx
push OFFSET FLAT:$SG539
call _printf
; Line 14
mov esi, DWORD PTR [esi+4]
add esp, 12 ; 0000000cH
test esi, esi
jne SHORT $L548
$L551:
pop esi
; Line 15
ret 0
?Traverse@@YAXPAVNode@@@Z ENDP ; Traverse

So basically what it does, rather than pushing the arguments and using a call on the second call to Traversal, it just replaces the original argument on the stack with the new argument and jumps to the top of the call.
However, the first call to traversal still has the normal calling overhead. So a completely psycho hand coded assembly implementation will still beat out the MSVC optimized version, but the MSVC optimized version still beats a naive assembly implementation of the code.

quote:

Doesn''t a bsp''s do most of its work by virtual of the transversal? ie little work to do when visiting a node, it''s either in or out, next node?


If I understand what you''re saying (by virtual??) then usually to use a BSP, you start at the top and move to one side or the other, and don''t need to do a full in-order traversal. However, traversals are still useful. Ex: dumping debugging information to a log file.
msn12b
msn12b
quote:
Original post by monkeyman
Gee, you don''t sound like such a dick after all



Yeah, well, I was in a pretty bad mood for a month before and after I posted that rant. It wasn''t meant to be constructive at all.

I still get pissed off at stupid questions, but implementing a recursive algorithm iteratively is hardly a stupid question.

Anyway, glad I could help.

MSN
Shannon Barber
Shannon Barber
I beleive I ment virtue, not virtual...

...
You don't thread the tree right before you transverse it!

You have to thread the tree as you build it, whenever you add something or remove soemthing, you have to manipulate the next pointers that are effected by the change. Thats that hard part of the threading.



And I don't think threading a tree is in any way like a stack; it is a linked list of all the bsp nodes, intertwined with the tree itself. ...because you twiddle the next pointers during the manipulation of the tree, it's not a stack. Stuff doesn't just go on one end and come off the same end. It gets twiddled all over the place, mostly in the middle, which is not the behavior of a stack. It is a good way to transverse a tree in-order (which recursion does not do at all!), but that doesn't help with a BSP.


...
Wait a minute, why do you have to transverse the *BSP* tree?
ooooohhhhhhh.... I'm not sure threading the tree will solve this problem. You only need to transverse a portion of the BSP tree, not the whole thing.
The BSP should always be in order, correct? It's not like a BST where the next node could be damn near anywhere. In a BSP are not all neighbooring nodes are either a child or a parent?

If I was more familar with BSP'ing I might be of more help...

Exactly what kind of transversal is needed? It seems to me that you first need to search the tree, then iterate a sub-tree. Depending on the search criteria, you may be able to search with a simple loop. If the iteration needs to be in-order by the traditional notion of an in-order transveral, then you need to thread the tree. If you can use any 'ole transversal, so long as you get everything in the sub-tree, I think you can use a while and a bunch of if's.

Edited by - Magmai Kai Holmlor on December 5, 2000 10:11:41 PM
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.