0

I am solving a system of coupled ordinary differential equations, and between runs of my program I wish to change which function to integrate. As of now, I simply comment and un-comment three return statements to toggle between the different functions, like this:

arma::vec acceleration(arma::vec u, double t)
{   /*
    The current acceleration of the system.
    */

    // return acceleration_1(u);
    // return acceleration_2(u);
    return acceleration_3(u);
}

acceleration is called many times, and I would like to make the code efficient, but I would also like to change which function to integrate with an input parameter, and not by entering the depths of the code to comment and un-comment different statements.

If I were to send in a parameter, say choice, which can only be one of {1, 2, 3}, and choice never change during a run of the program, will the if statements slow down my program? Example:

arma::vec acceleration(arma::vec u, double t, int choice)
{   /*
    The current acceleration of the system.
    */

    if (choice == 1)
    {
        return acceleration_1(u);

    }

    else if (choice == 2)
    {
        return acceleration_2(u);

    }

    else if (choice == 3)
    {
        return acceleration_3(u);

    }
}

May a switch statement be better suited (faster) for this purpose? Any alternative options which do not slow down the code?

EDIT: acceleration and acceleration_x are member functions which make alternative solutions with function pointers hard.

  • 7
    Are you calling this a million times a second? Have you performance profiled the code? Is it a performance bottleneck? – Eljay Dec 08 '19 at 13:36
  • @Eljay The function is called approximately 1.5 million times per second. I have not done extensive profiling of the code, only a few simple timings. – Bobson Dugnut Dec 08 '19 at 14:04
  • If you have the possibility to make `choice` a `constexpr` known at compilation time, then it is likely that you will not have any performance penalty – Damien Dec 08 '19 at 18:09

1 Answers1

4

The short answer is, yes it will if you compare it to not having an if statement there at all. But that's not the answer you want.

Your program is compiled and is run on a CPU. You do not mention which CPU architecture your project targets, but most modern CPUs use branch prediction, which helps speed-up cases of naive condition checking when the condition may vary at runtime.

Even if in your case, as you say, the choice variable will not change during the program lifetime, without branch prediction a compiler and the CPU wouldn't have much of a chance to avoid checking it every time you instruct so in your source code. It's a run-time variable and with C++ there are no constructs in place to help the compiler or the CPU know when it will change. They may use clever heuristics -- like substitute a number of if statements with a call dispatch (a function call), but that would be the same if you did the following:

arma::vec (*chosen_acceleration_proc)(arma::vec, double);

arma::vec acceleration(arma::vec u, double t)
{
    return chosen_acceleration_proc(u, t);
}

where you could switch chosen procedure at runtime:

chosen_acceleration_proc = acceleration_1; /// For example

The above will prevent the compiler from inlining your different acceleration procedures that it otherwise could. However, the only additional cost of your acceleration procedure is invoking chosen_acceleration_proc. The cost of completing the chosen acceleration procedure may far outweigh the cost of invoking it. The bottomline here is that if the acceleration procedures could benefit from inlining, using procedure pointers is an ill-fit approach.

You'd have to profile your compiled program to see if the cost of such dispatch is negligible or significant.

Back to branch prediction, for a CPU with branch-prediction, the CPU will "learn" after making the same choice certain number of times:

arma::vec acceleration(arma::vec u, double t) {
    switch(acceleration_proc_id) {
        case 1: return acceleration_1(u, t);
        case 2: return acceleration_2(u, t);
        /// etc
    }
}

...meaning that when you set the acceleration_proc_id to some chosen value, after some time, the CPU will just assume that it's going to stay that and prefetch and speculatively execute instructions as if that choice was a given. See this most excellent answer for more about branch prediction and how it helps or fails.

A switch is almost always a better choice than using if and else, it certainly is in your case. That's partially because one may only use constant expressions to switch on, which is useful for optimization to the compiler. There are other reasons.

What if the different procedures are all members of a class?

You can still use pointers -- to member procedures -- if the different acceleration computing procedures belong to the same class or derivatives of it. If they're all different members with identical signature, in the same class:

class Foo {
    arma::vec acceleration1(arma::vec u, double t);
    arma::vec acceleration2(arma::vec u, double t);
    arma::vec acceleration3(arma::vec u, double t);
}

arma::vec (Foo::*chosen_acceleration_proc)(arma::vec u, double t);

Foo obj;

chosen_acceleration_proc = &Foo::acceleration2; /// Specify your preferred procedure

arma::vec acceleration(arma::vec, double t) {
    return (obj.*chosen_acceleration_proc)(u, t);
}

Regardless of how you design your application -- whether it's an "application" object that contains the different acceleration procedures, or some other semantics apply -- you can just point a pointer like above to the desired procedure, this isn't really any different than with "ordinary" procedures.

To conclude, the best code a CPU can run is no code at all -- if you need to switch at runtime or call actual, non-inlined, procedures, the cost will be there, as opposed to using pre-processor or constant expressions to decide which acceleration function you want as the program is compiled.

You ultimately need to profile your program that uses switch vs. one that uses procedure pointers, or whatever else method you can conjure that I haven't covered. Profiling will determine where CPU spends most of its time anyway. If after profiling you conclude that the cost of being able to decide on acceleration procedure once at run-time, is negligible compared to actually computing acceleration, then just write the most readable, shortest, and simplest code you can write and leave the rest to the compiler -- that's its job.

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
  • 1
    Thank you for the detailed answer. I will do some profiling on the different solutions. Your proposed procedure switching solution may not work due to the fact that ```acceleration_x``` are member functions. I have added the information to the main post. – Bobson Dugnut Dec 08 '19 at 14:17
  • Nothing really changes if they are member functions, except that you also then can design your solution using virtual member functions. Do you want me to edit the answer? – Armen Michaeli Dec 08 '19 at 14:50
  • @BobsonDugnut Are they members of the same class? – Armen Michaeli Dec 08 '19 at 15:00
  • 1
    All of the accelerations are members of the same class, yes. I see you have updated the answer. I will try your solution now and do some profiling. Thank you. – Bobson Dugnut Dec 09 '19 at 10:56