Friday, July 15, 2011

Order Optimization

Order notation is a field of computer science/statistics with a goal to optimize algorithms such that they execute in as few cycles as possible O(x).  Without going into too much detail, the number of iterations it takes for an algorithm to finish with the correct output varies based on the algorithm itself, the best case input, the worst case input, and the statistical long term average case input.

College computer science classes focus on optimizing code such that the worst case number of cycles is as small as possible so that the code executes faster.  With this in mind, I spent the better part of three days this week writing the driver for a product I'm designing to have O(N) instead of O(N^2).  In theory, my code would run faster because I'd run my algorithm fewer times (up to 24 instead of up to 16,777,216).  So I plugged and I chugged and I theorized an awesome boolean search algorithm to successively approximate the answer, which worked on paper, so I wrote the code and thought it worked, and then I kept finding weird little pockets of input values that didn't output the correct array.  I slowly chugged and troubleshot these corner cases, and then I only had 216 cases left (out of up to 16,777,216) and suddenly I realized I had added so much complexity that my O(N) algorithm approached (but did not meet) the juvenile O(N^2) brute force method AND it took forever to execute due to the recursive branching nature of the algorithm followed by a parametric lookup table to verify the selection...

... so today, after 24 man hours on the algorithm, I got on my bike and rode home.  With only 3 hours of sleep the night before, in my delusional state I conjured up an O(N^2) algorithm so simple that it's per iteration execution time had to be miniscule.  I pumped my tired legs home and launched LabVIEW.  Twenty minutes later I had the brute force algorithm cranking away.  The function takes 0.017 seconds to initialize and then takes 0.0000008 seconds each time it is called after the first time.

Compare this to the juggernaut code I narrow-mindedly created, which takes 0.0001 seconds to initialize, but then takes 0.0001 seconds each time it is called after the first time.

Moral of the story: Don't let education box you into an answer.  I focused on my teaching and designed a terribly complicated algorithm that executed as few times as possible, but which took forever.  The better solution was to design a terribly simple algorithm that runs numerous times, but executes as fast as possible.  Obviously, there's a tradeoff between the number of elements you're looking at, but in my case the simple algorithm's inputs never exceed 2^24 elements, and thus never takes longer than the complex algorithm to execute.

Also, note that in 20 minutes I replaced three days of work.  It's important to be able to recognize when the path you're on is not the one you want to get to the end of.  The new algorithm is so simple it needs almost no verification compared to the previous one.  So now I can focus on everything else that's stressing me out trying to get this product shipping.