Return value optimization and copy elision in C
Asked Answered
E

2

76

Some people are not aware that it's possible to pass and return structs by value in C. My question is about the compiler making unnecessary copies when returning structs in C. Do C compilers such as GCC use Return value optimization(RVO) optimization or is this a C++ only concept? Everything I have read about RVO and copy elision is in regards to C++.

Let's consider an example. I'm currently implementing a double-double data type in C (or rather float-float to start with because I find it easy to unit test). Consider the following code.

typedef struct {
    float hi;
    float lo;
} doublefloat;

doublefloat quick_two_sum(float a, float b) {
    float s = a + b;
    float e = b - (s - a);
    return (doublefloat){s, e};
}

Will the compiler make a temporary copy of the doublefloat value I return or can the temporary copy be elided?

What about named return value optimization (NRVO) in C? I have another function

doublefloat df64_add(doublefloat a, doublefloat b) {
    doublefloat s, t;
    s = two_sum(a.hi, b.hi);
    t = two_sum(a.lo, b.lo);
    s.lo += t.hi;
    s = quick_two_sum(s.hi, s.lo);
    s.lo += t.lo;
    s = quick_two_sum(s.hi, s.lo);
    return s;
}

In this case i'm returning a named struct. Can the temporary copy in this case be elided?

It should be stated that this is a general question for C and that the code examples I have used here are only examples (when I optimize this I will be using SIMD with intrinsics anyway). I'm aware that I could look at the assembly output to see what the compiler does but I think this is an interesting question nevertheless.

Enthrone answered 4/5, 2015 at 15:41 Comment(14)
@BaummitAugen, I was not sure either if I should use the C++ tag. But I think I made it clear in my question that it's about C. I was hoping the C++ tag would attract people who are experts in both languages.Enthrone
@BaummitAugen is there such concept in C at all. I have removed the C tag as it seems erreanous to me.Chummy
@IvayloStrandjev still the question is about C, the tag applies to the question, no?Nucleolar
@IvayloStrandjev The question is: Do we have RVO in C? Even if the answer was "No", the C tag definitely applies because he asks about C.Earflap
@Zboson What isn't clear is, what detector is that ZZ event from?Gerry
@juanchopanza, haha! Good that you understand! I did my thesis on ZZ to four leptons. I think that one's from CDF but I did not do my thesis on CDF (I just found the picture from google).Enthrone
@Zboson Well I guess the tag is fine for the implicit and answered question "Why do I only find information regarding C++?".Earflap
Can the temporary copy in this case be elided? I wouldn't be the least bit surprised if this optimization existed in commercial C compilers dating back 20 or so years ago. It just got fancied up by C++ because, as the answer mentioned, C++ has side-effects to consider.Jourdan
I think the real question is why you would do that, or want to do it, in the first place? Seems to break my whole mental model of how C works - that is, pretty much everything is a pointer - while adding a bunch of work 'under the hood' that might well have a negative impact on performance.Convolvulaceous
@Convolvulaceous Wouldn't the question then rather be "if you do that, why are you using C?" ;)Jsandye
@jamesqf: the obvious answer would involve something about C being defined by the C standard, not your personal mental model.Shotgun
@PaulMcKenzie, I expect that you're right about this optimization going back 20 years. However, sometimes in those last 20 years many people grew up on C++ instead of C and did not learn to think like C coders.Enthrone
@jamesqf, returning these structs by value in my examples I think is more readable and more logical and not necessarily less efficient. I used to think like you. That's the reason I asked this question. My mental model of C is evolving. I would now make the counter argument that using a pointer is a premature optimization and potentially less efficient (IMHO you should only optimize what the compiler can't do not what it can do). I'm still thinking about this.Enthrone
Mission critical software has no heap allocation. C or not C. Also. One can try and compete with C++ or not GNUC, and he will be treated with respect. But he will never win that contract.Retardation
S
55

RVO/NRVO are clearly allowed under the "as-if" rule in C.

In C++ you can get observable side-effects because you've overloaded the constructor, destructor, and/or assignment operator to give those side effects (e.g., print something out when one of those operations happens), but in C you don't have any ability to overload those operators, and the built-in ones have no observable side effects.

Without overloading them, you get no observable side-effects from copy elision, and therefore nothing to stop a compiler from doing it.

Shotgun answered 4/5, 2015 at 15:46 Comment(5)
The address of the variable in the function, and the address of the variable assigned to outside, can be identical as they have non-overlapping lifetimes: the temporary bridge cannot have its address taken, and it sharing the other two variable's address cannot be detected. This, I think, makes it impossible to detect it happened (in theory) under the standard: in practice, if the variable in the function and the variable assigned to outside have the same address, odds are you are witnessing NRVO.Justifiable
Testing this out on gcc, g++, clang, clang++, it turns out that everyone does NRVO properly except gcc -xc, which generates superfluous copies when you have: struct s f() { struct s x = g(); return x; }Elsewhere
@Yakk-AdamNevraumont The address of the variable in the function, and the address of the variable assigned to outside, can be identical as they have non-overlapping lifetimes Do s and ret have non-overlapping lifetimes or Clang messed up and made NRVO visible? godbolt.org/z/Ef4sxseTjGoldsmith
I think this may be LLVM semantics, mimicking C++ one, and can sometimes misbehave for C programs. But nobody cares.Goldsmith
@LanguageLawyer I don't know enough C to answer that question. Elision is real in C++; I know that some elision can be "as-if" permitted in C, I do not know how far it can go in C. I'd have to understand what addresses mean in C (as opposed to C++) at a far deeper level than I do now.Justifiable
W
38

The reason why it's covered a lot for C++ is because in C++, RVO has side effects (ie. not calling the destructor of the temporary objects nor the copy constructor or assignment operator of the resulting objects).

In C, there's no possible side effect, only potential performance improvements. I see no reason such an optimization couldn't be performed by some compiler. At least, there is nothing that forbids it in the standard.

Anyway, optimization is compiler and optimization level-dependant, so I wouldn't bet on it for critical code paths, unless the compiler used is well defined and not expected to change (which is still often the case).

Woolcott answered 4/5, 2015 at 15:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.