views:

220

answers:

5

I am currently writing various optimizations for some code. Each of theses optimizations has a big impact on the code efficiency (hopefully) but also on the source code. However I want to keep the possibility to enable and disable any of them for benchmarking purpose.

I traditionally use the #ifdef OPTIM_X_ENABLE/#else/#endif method, but the code quickly become too hard to maintain.

One can also create SCM branches for each optimizations. It's much better for code readability until you want to enable or disable more than a single optimization.

Is there any other and hopefully better way work with optimizations ?

EDIT : Some optimizations cannot work simultaneously. I may need to disable an old optimization to bench a new one and see which one I should keep.

+8  A: 

I would create a branch for an optimization, benchmark it until you know it has a significant improvement, and then simply merge it back to trunk. I wouldn't bother with the #ifdefs once it's back on trunk; why would you need to disable it once you know it's good? You always have the repository history if you want to be able to rollback a particular change.

Oli Charlesworth
This approach also has it's limits. What if you want to optimize for different platforms, considering different OS or harware. Then you have to keep all branches (or #ifdefs) with a risk of diverging code with no possibility of merging back.
kriss
@kriss: I agree! But if this is the scenario, the questioner ought to state this in the question...
Oli Charlesworth
@Oli, Sometime two optimizations cannot work simultaneously. I may need to disable an old optimization to bench a new one and see which one I should keep.
Ben
@Ben: My personal preference is not to keep all this sort of infrastructure cluttering up the code just in case I want to experiment with it. I'm a firm believer that this is one reason that source repositories exist.
Oli Charlesworth
Yes, definitely I'm also a firm believer that we must kill dead code and that does not only apply to optimizations.
kriss
+1  A: 

I would work at class level (or file level for C) and embed all the various versions in the same working software (no #ifdef) and choose one implementation or the other at runtime through some configuration file or command line options. It should be quite easy as optimizations should not change anything at internal API level.

Another way if you'are using C++ can be to instantiate templates to avoid duplicating high level code or selecting a branch at run-time (even if this is often an acceptable option, some switches here and there are often not such a big issue).

In the end various optimized backend could eventually be turned to libraries.

Unit Tests should be able to work without modifying them with every variant of implementation.

My rationale is that embedding every variant mostly change software size, and it's very rarely a problem. This approach also has other benefits : you can take care easily of changing environment. An optimization for some OS or some hardware may not be one on another. In many cases it will even be easy to choose the best version at runtime.

kriss
The problem with this approach is that if you're optimising, say, the body of a tight inner loop, then either you incur overhead in conditionally branching to each of the two implementations on every iteration, or you end up duplicating the higher-level code.
Oli Charlesworth
@Oli Charlesworth: you're right, that's the limit of this solution and I updated my answer accordingly. However it's very rare to actually encounter this case. If timing is as tight as this can be an issue you should probably consider refactoring both inner loop and caller as new possible optimizations often appears doing this. In other words, if this is really an issue you're probably optimizing at the wrong level. There is another very interesting answer on this subject on SO, I will try to find it again to provide a link.
kriss
@Oli Charlesworth: OK, I found it http://stackoverflow.com/questions/926266/performance-optimization-strategies-of-last-resort this answer is one of my favorite favorites. :-)
kriss
+3  A: 

There are so many ways of choosing which part of your code that will execute. Conditional inclusion using the preprocessor is usually the hardest to maintain, in my experience. So try to minimize that, if you can. You can separate the functionality (optimized, unoptimized) in different functions. Then call the functions conditionally depending on a flag. Or you can create an inheritance hierarchy and use virtual dispatch. Of course it depends on your particular situation. Perhaps if you could describe it in more detail you would get better answers.

However, here's a simple method that might work for you: Create two sets of functions (or classes, whichever paradigm you are using). Separate the functions into different namespaces, one for optimized code and one for readable code. Then simply choose which set to use by conditionally using them. Something like this:

#include <iostream>
#include "optimized.h"
#include "readable.h"

#define USE_OPTIMIZED

#if defined(USE_OPTIMIZED)
using namespace optimized;
#else
using namespace readable;
#endif

int main()
{
   f();
}

Then in optimized.h:

namespace optimized
{
   void f() { std::cout << "optimized selected" << std::endl; }
}

and in readable.h:

namespace readable
{
   void f() { std::cout << "readable selected" << std::endl; }
}

This method does unfortunately need to use the preprocessor, but the usage is minimal. Of course you can improve this by introducing a wrapper header:

wrapper.h:

#include "optimized.h"
#include "readable.h"

#define USE_OPTIMIZED

#if defined(USE_OPTIMIZED)
using namespace optimized;
#else
using namespace readable;
#endif

Now simply include this header and further minimize the potential preprocessor usage. Btw, the usual separation of header/cpp should still be done.

Good luck!

Daniel Lidström
interesting idea ! thanks
Ben
A: 

I'm confused. Why don't you just find out where each performance problem is, fix it, and continue. Here's an example.

Mike Dunlavey
A: 

You may have two (three/more) version of function you optimise with names like: function function_optimized which have identical arguments and return same results.

Then you may #define selector in som header like:

 #if OPTIM_X_ENABLE
 #define OPT(f) f##_optimized
 #else
 #define OPT(f) f
 #endif

Then call functions having optimized variants as OPT(funtion)(argument, argument...). This method is not so aestetic but it does so.

You may go further and use re#define names for all your optimized functions:

 #if OPTIM_X_ENABLE
 #define foo foo_optimized
 #define bar bar_optimized
 ...
 #endif

And leave caller code as is. Preprocessor does function substitution for you. I like it most, because it works transparently while per-function (and also per datatype and per variable) grained which is enough in most cases for me.

More exotic method is to make separate .c file for non-optimized and optimized code and compile only one of them. They may have same names but with different paths, so switching can be made by change single option in command line.

Vovanium