Search code examples
c++dispatchbranch-predictionnull-object-pattern

Branch on null vs null object performance


Which is most efficient: using a null object, or a branch on nullptr. Example in C++:

void (*callback)() = [](){}; // Could be a class member

void doDoStuff()
    {
    // Some code
    callback();  // Always OK. Defaults to nop
    // More code
    }

vs

void (*callback)() = nullptr; // Could be a class member

void doDoStuff()
    {
    // Some code
    if(callback != nullptr)  // Check if we should do something or not
        {callback();}
    // More code
    }

The null object will always do an indirect function call, assuming the compiler cannot inline it. Using a nullptr, will always do branch, and if there is something to do, it will also do an indirect function call.

Would replacing callback with a pointer to an abstract base class affect the decision?

What about the likelihood of callback set to something other than nullptr. I guess that if callback is most likely nullptr, then it is faster with the additional branch, right?


Solution

  • Which is most efficient: using a null object, or a branch on nullptr.

    Assuming a given codegen solution, it will depend on your target. And, if you don't assume that, it will also depend on your code, compiler and probably moon phase.

    Would replacing callback with a pointer to an abstract base class affect the decision?

    In most scenarios that will end up being also dynamic dispatch.

    What about the likelihood of callback set to something other than nullptr. I guess that if callback is most likely nullptr, then it is faster with the additional branch, right?

    That would depend on the ratio and also on the hardware (whether a branch is faster or not compared to a indirect function call; which also depends on the ratio).


    Just to make you aware of how subtle things are: let's assume you are talking about modern x86_64 desktop processors with the call not being inlined and with a likelihood of 99% of the time being nullptr. People told you to measure.

    So you did and, in this case, let's assume you found out the branch seems faster. Great! So we should start using branches everywhere, right?

    Not really, because your branch predictor will have likely made the code effectively free (branch never taken), which means you were not even comparing branches vs. indirect calls at all.