4

Let's say we have two functions:

int f();
int g();

I want to get the sum of f() and g().

First way:

int fRes = f();
int gRes = g();
int sum = fRes + gRes;

Second way:

int sum = f() + g();

Will be there any difference in performance in this two cases?

Same question for complex types instead of ints

EDIT

Do I understand right i should not worry about performance in such case (in each situation including frequently performed tasks) and use temporary variables to increase readability and to simplify the code ?

Andrew
  • 24,218
  • 13
  • 61
  • 90
  • 3
    Wrong question. The question should be "should I worry about performance here", to which the answer is NO WAI. –  Feb 21 '11 at 22:21

7 Answers7

10

You can answer questions like this for yourself by compiling to assembly language (with optimization on, of course) and inspecting the output. If I flesh your example out to a complete, compilable program...

extern int f();
extern int g();
int direct()
{ 
  return f() + g(); 
}
int indirect()
{
  int F = f();
  int G = g();
  return F + G;
}

and compile it (g++ -S -O2 -fomit-frame-pointer -fno-exceptions test.cc; the last two switches eliminate a bunch of distractions from the output), I get this (further distractions deleted):

__Z8indirectv:
        pushq   %rbx
        call    __Z1fv
        movl    %eax, %ebx
        call    __Z1gv
        addl    %ebx, %eax
        popq    %rbx
        ret

__Z6directv:
        pushq   %rbx
        call    __Z1fv
        movl    %eax, %ebx
        call    __Z1gv
        addl    %ebx, %eax
        popq    %rbx
        ret

As you can see, the code generated for both functions is identical, so the answer to your question is no, there will be no performance difference. Now let's look at complex numbers -- same code, but s/int/std::complex<double>/g throughout and #include <complex> at the top; same compilation switches --

__Z8indirectv:
        subq    $72, %rsp
        call    __Z1fv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 48(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 56(%rsp)
        call    __Z1gv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 32(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 40(%rsp)
        movsd   48(%rsp), %xmm0
        addsd   32(%rsp), %xmm0
        movsd   56(%rsp), %xmm1
        addsd   40(%rsp), %xmm1
        addq    $72, %rsp
        ret

__Z6directv:
        subq    $72, %rsp
        call    __Z1gv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 32(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 40(%rsp)
        call    __Z1fv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 48(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 56(%rsp)
        movsd   48(%rsp), %xmm0
        addsd   32(%rsp), %xmm0
        movsd   56(%rsp), %xmm1
        addsd   40(%rsp), %xmm1
        addq    $72, %rsp
        ret

That's a lot more instructions and the compiler isn't doing a perfect optimization job, it looks like, but nonetheless the code generated for both functions is identical.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • +1 for practical evidence. It's important to note that compilers often get the gist of what you're doing and will produce the same results, allowing you to make code more readable. – Sion Sheevok Feb 21 '11 at 22:40
  • Does not prove though that the generated code must be the same in both cases. – Maxim Egorushkin Feb 22 '11 at 10:02
  • Well, no, there's no *guarantee* that the generated code will be the same. If you were to use gcc's `-O0` rather than `-O2` I would expect to see differences. But generating equivalent code for these is one of the most basic optimizations in the book (it's usually called "forward propagation") and a modern compiler that doesn't do it, when optimizations are on, is not fit for purpose. – zwol Feb 22 '11 at 16:20
4

I think in the second way it is assigned to a temporary variable when the function returns a value anyway. However, it becomes somewhat significant when you need to use the values from f() and g() more than once case in which storing them to a variable instead of recalculating them each time can help.

Argote
  • 2,155
  • 1
  • 15
  • 20
2

If you have optimization turned off, there likely will be. If you have it turned on, they will likely result in identical code. This is especially true of you label the fRes and gRes as const.

Because it's legal for the compiler to elide the call to the copy constructor if fRes and gRes are complex types they will not differ in performance for complex types either.

Someone mentioned using fRes and gRes more than once. And of course, this is obviously potentially less optimal as you would have to call f() or g() more than once.

Community
  • 1
  • 1
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
2

As you wrote it, there's only a subtle difference (which another answer addresses, that there's a sequence point in the one vs the other).

They would be different if you had done this instead:

int fRes;
int gRes;
fRes = f();
fRes = g();
int sum = fRes + gRes;

(Imagining that int as actually some other type with a non-trivial default constructor.)

In the case here, you invoke default constructors and then assignment operators, which is potentially more work.

rlibby
  • 5,931
  • 20
  • 25
1

It depends entirely on what optimizations the compiler performs. The two could compile to slightly different or exactly the same bytecode. Even if slightly different, you couldn't measure a statistically significant difference in time and space costs for those particular samples.

outis
  • 75,655
  • 22
  • 151
  • 221
1

On my platform with full optimization turned on, a function returning the sum from both different cases compiled to exactly the same machine code.

The only minor difference between the two examples is that the first guarantees the order in which f() and g() are called, so in theory the second allows the compiler slightly more flexibility. Whether this ever makes a difference would depend on what f() and g() actually do and, perhaps, whether they can be inlined.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
0

There is a slight difference between the two examples. In expression f() + g() there is no sequence point, whereas when the calls are made in different statements there are sequence points at the end of each statement.

The absence of a sequence point means the order these two functions are called is unspecified, they can be called in any order, which might help the compiler optimize it.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271