0

In my C++ code, I have to select between two functions that take the same arguments based on a given condition. I could write:

if (condition) 
   return foo(a, b, c);
else
   return bar(a, b, c);

Or:

return (condition? foo : bar)(a, b, c);

But which of these two ways is faster?

EDIT:

I tried to test using this code:

#include <cmath>
#include <chrono>
#include <iostream>

using namespace std;

void foo(float x, float y, int param)
{
    pow(x+y, param);
}

void bar(float x, float y, int param)
{
    pow(x+y, param);
}

int main() {

    const int loops = 1000;

    auto t1 = chrono::high_resolution_clock::now();
    for(int i = 0; i < loops; i++)
        for(int j = 0; j < loops; j++)
            (i==j? foo : bar)(i, j, 2);

    auto t2 = chrono::high_resolution_clock::now();

    for(int i = 0; i < loops; i++)
        for(int j = 0; j < loops; j++)
            if(i==j)
                foo(i, j, 2);
            else
                bar(i, j, 2);

    auto t3 = chrono::high_resolution_clock::now();

    cout << "ternary: " << (chrono::duration_cast<chrono::microseconds>(t2-t1).count()) << "us" << endl;
    cout << "if-else: " << (chrono::duration_cast<chrono::microseconds>(t3-t2).count()) << "us" << endl;
    return 0;
}

With the updated test code I got:

 ternary: 70951us
 if-else: 67962us
Hydren
  • 363
  • 2
  • 11
  • 15
    Any decent compiler will output the same assembly for both. This is next-level premature optimization and something you shouldn't care about. Pick the one that is most readable and fits into your project style. – Hatted Rooster Aug 14 '19 at 18:35
  • 1
    Any benchmarks you did so far? Why would you assume that either method should be superior about the other one. Your question is unclear. – πάντα ῥεῖ Aug 14 '19 at 18:37
  • @πάνταῥεῖ Yes, but the results seem inconsistent. – Hydren Aug 14 '19 at 18:40
  • @Hydren what are the signatures for `foo` and `bar`? Can you show an example where the results are inconsistent? – Ryan Haining Aug 14 '19 at 18:41
  • @SombreroChicken Your answer has been migrated to the proper place. – Lightness Races in Orbit Aug 14 '19 at 18:41
  • @Hydren Provide a [mcve] to show the inconsitency. – πάντα ῥεῖ Aug 14 '19 at 18:41
  • 1
    @SombreroChicken Looks like [gcc](https://godbolt.org/z/R-qR__) and [clang](https://godbolt.org/z/_frz38) both produce different assembly for the two cases though it's not clear to me if there is any real performance difference. Notice that the one with the conditional operator first determines which function to call, then actually performs the call outside of the conditional branch. – François Andrieux Aug 14 '19 at 18:43
  • @πάνταῥεῖ This was the [code](http://codepad.org/RdX4UTnx) I used to test – Hydren Aug 14 '19 at 18:44
  • @Hydren [Edit] your question please! Your links don't help much. – πάντα ῥεῖ Aug 14 '19 at 18:46
  • 5
    `time_t` is a blunt force instrument when benchmarking. It provides seconds. Next, the cost of the conditional is insignificant compared to the cost of the writing to the console. I think you are mostly measuring `cout << blah`. – user4581301 Aug 14 '19 at 18:52
  • @Hydren 1st you ask about perfomance, then you ask about result correcteness? Be more specific please! – πάντα ῥεῖ Aug 14 '19 at 19:00
  • @πάνταῥεῖ I'm asking about performance. I'm not sure when I asked about correcteness. – Hydren Aug 14 '19 at 19:04
  • @Hydren _"Yes, but the results seem inconsistent."_ Explain inconsitency then please. Your question is still unclear. – πάντα ῥεῖ Aug 14 '19 at 19:05
  • with both [gcc](https://wandbox.org/permlink/VlwFykoVC6J2d9Kd) and [clang](https://wandbox.org/permlink/TU7OCN83ONU46y01), and a more useful version of OP's code, if-else seems to perform slightly better consistently, but negligibly so. Without optimizations the gap is a bit wider. – Ryan Haining Aug 14 '19 at 19:10
  • @πάνταῥεῖ I meant the *performance results* of my tests seemed inconsistent. Sometimes the ternary ran faster, sometimes the if-else ran faster, sometimes equal... – Hydren Aug 14 '19 at 19:13
  • @RyanHaining, can you provide an improved of my test code, so I can edit the question with it? – Hydren Aug 14 '19 at 19:15
  • 1
    @Hydren First, you should get rid of all the `cout`s within the timed sections of your code. Printing is comparatively slow, so you are likely just timing how long it takes to print your output. – François Andrieux Aug 14 '19 at 19:16
  • @Hydren click the links in my previous comment. global `/dev/null` object is a bad idea typically, I just used it to be fast – Ryan Haining Aug 14 '19 at 19:17
  • @Hydren Again: Show your benchmarks that show the performance difference. – πάντα ῥεῖ Aug 14 '19 at 19:21
  • Your `if` statement will compile into assembly language as: compare; branch if false to A:, call foo; jump Around; A: call bar; Around: return; That's not many instructions. You bottleneck is elsewhere. Your ternary operator should evaluate to the same code. – Thomas Matthews Aug 14 '19 at 19:53
  • 1
    Regarding the revised code: `pow` is not as expensive as `cout << blah << pow`, but `pow` is still quite expensive and will be drowning the minor difference between the two conditionals. To be honest, the difference is also probably lost in the expense of the for loops. If there is any, it's really, really small. You'll probably need billions and billions of iterations to be able to see it above the general noise of the system. – user4581301 Aug 14 '19 at 22:56

2 Answers2

1

Migrated from comments:

Any decent compiler will output the same assembly for both. This is next-level premature optimization and something you shouldn't care about. Pick the one that is most readable and fits into your project style. —Sombrero Chicken

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

There is no problem with the performance. The compiler can generate the same object code for the both cases.

However there is a difference in applying these constructions.

For the conditional operator there must be deduced the common type of its second and third operands.

For example if you have the following functions

void foo( int, int, int ) {}
int bar( int, int, int ) { return 10; } 

then you can use the if-statement as it is shown in the demonstrative program

#include <iostream>

void foo( int, int, int ) {}
int bar( int, int, int ) { return 10; }         

int main()
{
    int x;
    std::cin >> x;

    if ( x < 10 )
    {
        foo( x, x, x );
    }
    else
    {
        bar( x, x, x );
    }
}

However you may not write as it seems an equivalent code

#include <iostream>

void foo( int, int, int ) {}
int bar( int, int, int ) { return 10; }         

int main()
{
    int x;
    std::cin >> x;

    x < 10 ? foo( x, x, x ) : bar( x, x, x );
}

because the compiler is unable to deduce the common type of the calls foo( x, x, x ) and bar( x, x, x ). The first one has the type void and the second one has the type int.

Of course you could write instead

    x < 10 ? foo( x, x, x ) : ( void )bar( x, x, x );

but sometimes this makes code less readable when compound expressions are used. And moreover sometimes it is even impossible to deduce the common type because there is no explicit or implicit conversion between expressions especially for user-defined types.

Another example. You can write a function to allow the compiler to deduce its return type.

#include <iostream>

constexpr auto factorial( unsigned long long int n )
{
    if ( n < 2 )
    {
        return n;
    }
    else
    {
        return n * factorial( n - 1 );
    }        
}

int main()
{
    int a[factorial( 5 )];

    std::cout << "The array has " << sizeof( a ) / sizeof( *a ) << " elements\n";
}

The program output is

The array has 120 elements

On the other hand, you may not write

#include <iostream>

constexpr auto factorial( unsigned long long int n )
{
    return n < 2 ? n : n * factorial( n - 1 );
}

int main()
{
    int a[factorial( 5 )];

    std::cout << "The array has " << sizeof( a ) / sizeof( *a ) << " elements\n";
}

The compiler is unable to deduce the return type of the function.

So sometimes there is even no choice between whether to use the if-statement or the conditional expression:)

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335