 a computation that requires a large amount of memory: look at the parameters of the computation to see whether some memory use can be avoided for that problem instance.
Once you have found some of the patterns that appear most often, look at the code which is to be executed. Make a copy of it, select a common pattern, and start simplifying the copy based on what you know about the parameters in this pattern. Some calculations will simply disappear; others will require much less code because the relationships between parameters will now be known. Conditional logic will become much simpler as well. When you know that a condition is true or false, you no longer need to test for it. The code becomes more of a straight execution line, allowing you to simplify and combine even more.
Invoke the new function by adding code which tests for the common pattern in the original function. Do this for several common patterns, until you run out of good patterns or the original function begins to get cluttered.
Test each of the new code blocks to ensure they still perform the same function as the original, generalized code. Often you can do this by running the new code at the same time as the original - run the optimized version, then the general version, and compare results on the fly. These test runs will obviously be slower than the original, but you need to continue following the rule "make it right before making it faster."
Once you have some of the most common cases coded, tested, and integrated, stop invoking the generalized calculations and see how much improvement you get. You might find that you get no improvement whatsoever! This can happen when the specialized code is not much shorter than the generalized code, or when the code needed to select the specialized case has a high cost relative to the general calculation.
Sometimes the overhead of calling special-case routines can be high enough to notice, in which case you can bring the special-case code back into the original routine. This requires caution, because larger routines are harder to understand, debug, and maintain.
Even if the new specialized code is no faster than the original, there is still hope. Profiling of the new code may find hotspots within it that can be improved by code rewriting. You may also find that not all specializations are faster; since the generalized code is still available, you can revert to using it for the slower specializations. Each specialization is independent, so you can simply revert them one by one to see which ones improve runtime.
You may also find that some domain knowledge is helpful, especially if you are not the original author. Subtle relationships between the parameters can allow more simplification. Looking at the code by itself may not tell you about these relationships. Try to find another developer or a user who can give you guidance about the specializations you have identified.
Last but not least, once you have removed several special cases from the generalized code, you may find that it can be simplified as well. Do this cautiously, particularly if you have not yet seen any overall runtime improvement. Save a copy of the original code (you are using a version control system, right?) so you can go back to it. If you have been following my suggestions, up until now you have always had the original implementation available, but after you simplify that, it is hard to bring code back in.
You may find that removing some of the code from the general-purpose routine gives you a set of sleek routines that each do one thing well, rather than a single sluggish "kitchen sink" routine that tries to do everything. This can be an improvement in code quality in addition to runtime.
Downsides of Optimization by Pattern Matching
There are downsides to any optimization strategy, and pattern matching techniques increase the amount of code to write, debug and maintain. In particular, copying the master routine and specializing it violates the rule that you should do something in only one place. Make sure you document the original source of t