Looping Techniques in Houdini, Part One
I want to take a few minutes to go over some of the various looping techniques that I use in Houdini, their pros and cons, and some of the performance implications. I’ll go through an example with points and another with primitives. You’ll see that some of the techniques are overkill for these very basic examples. However, there are many scenarios that require the flexibility of the Foreach SOP or some Python code which I may go over in another post. I am going to keep this discussion mostly focused on performance.
First, we’ll consider a very simply example – we have a collection of points that we want to move upwards based on point numbers. I’ll start by just placing a grid, scattering 50,000 points, and sorting the points based on proximity to the origin (to give us a cool shape when we move the points). You’ll notice that I’m always multiplying the point position by 0.001 before adding – this is just a constant that helps keep the points in a pretty shape.
Point SOP: The most elegant way to move the points is with a simple Point SOP – all we need to do is enter $TY+$PT into the y-translation field, moving each point up by its point number. This is the most clear approach, but it lacks in flexibility in the case that we might want to perform more complex operations than just translation.
VOP SOP: Ideal for complex point operations, VOP SOPs are quite powerful. Despite being overkill for this situation, there are many scenarios which require the flexibility of VOP SOPs. This network simply adds the point number to the y-translation by breaking the position into components, adding to the y-component, and then putting them back together.
Foreach SOP: This technique becomes really useful when you want to loop through collections of points or primitives in complex ways, such as grouping by attribute value, looping through groups, or feeding the results of one iteration into the next. The Foreach SOP is one of the more complicated SOPs in Houdini – use it with caution. This example loops through each point and applies a transformation to the y-component.
Python SOP: This is essentially an example of when NOT to use Python operators in Houdini, but again I want to illustrate the differences with the other approaches. This code should be very straight forward if you’re familiar with Python – we just loop through the points and increment the y-position for each point.
Old School Loop: Back before the Foreach SOP was introduced, many people would use a combination delete node and copy node to loop through items. Without getting into too much detail about how this loop is set up, the delete node essentially isolates the current item and the copy node combines them all together. The nodes in between are what constitute the body of the loop. Note that I use a Blast SOP here. Many people default to the Delete SOP because it is more flexbile, but it is VERY slow for large point operations. In my tests, replacing the Delete SOP with the Blast SOP resulted in this loop running over 10x faster (111 seconds vs 8.9)!
Results and Performance:
Houdini has some great profiling tools. Using the Performance Monitor, we can compare the compute times of each node. As you can see below, the vopsop is far and above the fastest option presented here. It takes advantage of multi-threading, so it really is no competition when it comes to doing large scale point manipulation. I mean, this thing is lightning fast. Even at 5,000,000 points, the vopsop only took 0.6 seconds to compute.
In terms of real-world time, the point and python nodes finish close behind – you would only notice a difference on huge point sets. The Foreach and oldSchool approaches fall way behind, proving that they are really only useful for complex situations which we will cover next. The profile below was performed on 50,000 points.
For this next example, I scattered 20,000 primitives, each consisting of three points. In this case, I excluded the VOP SOP since it is meant for iterating through points. I had originally planned on excluding the Point SOP as well but I included the it because the attribute $PR (primitive number) is accessible – I’m surprised that this is the case given that this is not a point attribute. Either way, here are the results.
In this case, the Foreach and oldSchool occupied roughly the same spot as the slowest, while the Python loop was significantly faster, and the Point SOP even faster yet. Again, this is the same trend that we saw with the point example.
Through these tests, we have seen that as you generally step up in flexbility, you lose out on performance. Thus it makes sense to choose nodes that cover your needs as closely as possible. Don’t always use the most powerful/flexbile nodes because you’re familiar with them – try to take the time to learn the various nodes – for example, how to use both the Blast and Delete SOPs. On large data sets, making the right node choices can easily make or break your performance. Further, I recommend using VOP SOPs whenever you’re operating on very large data sets to take advantage of multithreading. Hopefully SideFX will implement multithreading for other nodes in the future.