4

In this talk (sorry about the sound) Chandler Carruth suggests not passing by reference, even const reference, in the vast majority of cases due to the way in which it limits the back-end to perform optimisation.

He claims that in most cases the copy is negligible - which I am happy to believe, most data structures/classes etc. have a very small part allocated on the stack - especially when compared with the back-end having to assume pointer aliasing and all the nasty things that could be done to a reference type.

Let's say that we have large object on the stack - say ~4kB and a function that does something to an instance of this object (assume free-standing function).

Classically I would write:

void DoSomething(ExpensiveType* inOut);
ExpensiveType data;
...
DoSomething(&data);

He's suggesting:

ExpensiveType DoSomething(ExpensiveType in);
ExpensiveType data;
...
data = DoSomething(data);

According to what I got from the talk, the second would tend to optimise better. Is there a limit to how big I make something like this though, or is the back-end copy-elision stuff just going to prefer the values in almost all cases?

EDIT: To clarify I'm interested in the whole system, since I feel that this would be a major change to the way I write code, I've had use of refs over values drilled into me for anything larger than integral types for a long time now.

EDIT2: I tested it too, results and code here. No competition really, as we've been taught for a long time, the pointer is a far faster method of doing things. What intrigues me now is why it was suggested during that talk that we move to pass by value, but as the numbers don't support it, it's not something I'm going to do.

Goobley
  • 331
  • 2
  • 8
  • 3
    Measure it, then tell us what you found. – Kerrek SB Sep 29 '15 at 13:24
  • Clearly the answer is _It depends_. What about unoptimized builds? Is the performance loss due to `const &` really that painful for you? Does it even apply to anything but clang and are you using clang? I would delay micro optimizations like that until they become necessary. – nwp Sep 29 '15 at 13:27
  • You can make links to a specific location in a youtube video if there's something specific in it you want to refer to. – molbdnilo Sep 29 '15 at 13:27
  • @nwp I'd rather call it a paradigm shift than a micro optimization. It was a no-no some time ago. – Peter - Reinstate Monica Sep 29 '15 at 13:29
  • I'd be frankly amazed if the second optimised down. Is there any indication that it will? I will definitely write `void DoSomething(ExpensiveType&)` here. – Lightness Races in Orbit Sep 29 '15 at 13:30
  • 6
    @KerrekSB, I dislike the "measure it" responses on SO. I mean, sure, measure it and let us know what was found, but measuring it only gives you one answer on one compiler on one platform. It may yield completely different results on a different setup. However, understanding the logic behind why certain things are fast or slow helps people make a decision. Even if its just a case of "Compiler X can optimise Y in Z ways" is more useful than "This code runs in ABC time when run under setup X". Profiling and measuring are important and useful tools, but there's more to the story than that. –  Sep 29 '15 at 13:31
  • 3
    @Dan: Well, sorry that you don't like the answer, but that doesn't change the fact that *it depends*, and you can only make an informed decision if you have the data. – Kerrek SB Sep 29 '15 at 13:34
  • He sums a fair amount of it up around the 48 min mark.I will do some proper measuring soon. But to my knowledge the major compilers apply similar optimisation algorithms thus I would expect (in general) what works for one to work the others. – Goobley Sep 29 '15 at 13:35
  • Shouldn't the answer depend a lot on whether `ExpensiveType` can be moved (for return value optimization) and, if not, how expensive the copy ctor is? Can a compiler always optimize away both nominal copies? – Peter - Reinstate Monica Sep 29 '15 at 13:35
  • @PeterSchneider: Somewhat, but copying the function argument is more in question. Everyone knows about RVO and moving, but neither are applicable to the function argument (which is also specifically what the OP is asking about). – Lightness Races in Orbit Sep 29 '15 at 13:36
  • 1
    @LightnessRacesinOrbit I believe the logic is that since the compiler can track the lifetime completely and does not need to worry about pointer aliasing (you're passing a "copy" after all), the compiler has more freedom to optimise both the parameter and return value. The theory is that a modern compiler will eliminate the copies in this case (although I suppose it does depend on the contents of the function) - they already do it for the return value: https://en.wikipedia.org/wiki/Return_value_optimization so perhaps they also do for the args (but I don't know if they do). –  Sep 29 '15 at 13:39
  • 1
    @LightnessRacesinOrbit The assembly from the deleted answer is irrelevant since it uses gcc at `O2`. Using clang with `O3` would give a more meaningful hint. The reason why it is worth it is that compilers can eliminate unneeded data from the call, inserting references where faster. The philosophy behind this idea is that the C++ code describes the programmer's intend and has nothing to do with what happens. The compiler then implements the programmer's intend as best as it can and pass by value captures the intend better than pass by reference. – nwp Sep 29 '15 at 13:41
  • @LightnessRacesinOrbit Passing by ref would obviate the need to return, so RVO incl movability *is* part of the answer (even if not of the question...). – Peter - Reinstate Monica Sep 29 '15 at 13:42
  • 1
    @nwp I would also assume that a compiler which can prove that secretly passing by ref is ok "as-if" (no concurrent access through aliases etc.), should logically be able to do the same if the user herself explicitly passes by ref. The compiler would just mimick the user's code, wouldn't it? No more reasoning necessary than with its own passing-by-ref. – Peter - Reinstate Monica Sep 29 '15 at 13:46
  • 2
    @nwp: I fail to see how passing by value when you don't want a copy "describes the programmer's intent". – Lightness Races in Orbit Sep 29 '15 at 13:50
  • 1
    @Dan: Right, exactly, the question is whether they do for the args. Personally I doubt it but do not know at all. Then add on what Peter just said. I really don't think the gamble is worth it in any way. Smells like cargo culting to me! – Lightness Races in Orbit Sep 29 '15 at 13:50
  • @PeterSchneider Chandler said in multiple talks that pointers are very difficult to optimize and pass by value is easy. Sadly I have nothing better. @LightnessRacesinOrbit Pass by value expresses that the function will not change the original object. This makes optimizing possible. With a pointer or `const &` the proof supposedly becomes very difficult. Chances are compilers will get smarter and this is just a temporary effect. – nwp Sep 29 '15 at 13:55
  • My personal opinion, for what matters, is that passing by value (when possible) is very much worth it, but not for reasons of optimization - it may well result in worse performance. Rather, because it makes it easier to reason about code, and allows for simpler guidelines on how to design functions. [This article](http://josephmansfield.uk/articles/need-value-pass-by-value.html) makes very good points IMO. – Andy Prowl Sep 29 '15 at 14:00
  • It looks like compiler programmers tend to explain that low-level optimisation should not be application programmers's problem. In that sense, I do agree. But when after profiling you have identified a bottleneck and want to manually low-level optimize it, such general rules no longer apply - but it is just an opinion so not an answer ;-) – Serge Ballesta Sep 29 '15 at 15:52
  • I've seen [*lots of "bottlenecks"*](http://stackoverflow.com/a/927773/23771), and *none* of them are due to the compiler failing to optimize. They are all due to things the programmer could do better, and the optimizer cannot help. My taste is that reference parameters are really useful for things that are a) really big, or b) represent output from the function. Compiler writers may find them difficult, but they are working on optimizations I (for one) have little occasion to care about. – Mike Dunlavey Sep 29 '15 at 18:27

2 Answers2

2

I have now watched parts of Chandler's talk. I think the general discussion along the lines "should I now always pass by value" does not do his talk justice. Edit: And actually his talk has been discussed before, here value semantics vs output params with large data structures and in a blog from Eric Niebler, http://ericniebler.com/2013/10/13/out-parameters-vs-move-semantics/.

Back to Chandler. In the key note he specifically (around the 4x-5x minute mark mentioned elsewhere) mentions the following points:

  • If the optimizer cannot see the code of the called function you have much bigger problems than passing refs or values. It pretty much prevents optimization. (There is a follow-up question at that point about link time optimization which may be discussed later, I don't know.)
  • He recommends the "new classical" way of returning values using move semantics. Instead of the old school way of passing a reference to an existing object as an in-out parameter the value should be constructed locally and moved out. The big advantage is that the optimizer can be sure that no part of the object is alisased since only the function has access to it.
  • He mentions threads, storing a variable's value in globals, and observable behaviour like output as examples for unknowns which prevent optimization when only refs/pointers are passed. I think an abstract description could be "the local code can not assume that local value changes are undetected elsewhere, and it cannot assume that a value which is not changed locally has not changed at all". With local copies these assumptions could be made.

Obviously, when passing (and possibly, if objects cannot be moved, when returning) by value, there is a trade-off between the copy cost and the optimization benefits. Size and other things making copying costly will tip the balance towards reference strategies, while lots of optimizable work on the object in the function tips it towards value passing. (His examples involved pointers to ints, not to 4k sized objects.)

Based on the parts I watched I do not think Chandler promoted passing by value as a one-fits-all strategy. I think he dissed passing by reference mostly in the context of passing an out parameter instead of returning a new object. His example was not about a function which modified an existing object.

On a general note:

A program should express the programmer's intent. If you need a copy, by all means do copy! If you want to modify an existing object, by all means use references or pointers. Only if side effects or run time behavior become unbearable; really only then try do do something smart.

One should also be aware that compiler optimizations are sometimes surprising. Other platforms, compilers, compiling options, class libraries or even just small changes in your own code may all prevent the compiler from coming to the rescue. The run-time cost of the change would in many cases come totally unexpected.

Community
  • 1
  • 1
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
0

Perhaps you took that part of the talk out of context, or something. For large objects, typically it depends on whether the function needs a copy of the object or not. For example:

ExpensiveType DoSomething(ExpensiveType in) 
{
    cout << in.member;
}

you wasted a lot of resource copying the object unnecessarily, when you could have passed by const reference instead.

But if the function is:

ExpensiveType DoSomething(ExpensiveType in) 
{
    in.member = 5;
    do_something_else(in);
}

and we did not want to modify the calling function's object, then this code is likely to be more efficient than:

ExpensiveType DoSomething(ExpensiveType const &inr) 
{
    ExpensiveType in = inr;
    in.member = 5;
    do_something_else(in);
}

The difference comes when invoked with an rvalue (e.g. DoSomething( ExpensiveType(6) ); The latter creates a temporary , makes a copy, then destroys both; whereas the former will create a temporary and use that to move-construct in. (I think this can even undergo copy elision).

NB. Don't use pointers as a hack to implement pass-by-reference. C++ has native pass by reference.

M.M
  • 138,810
  • 21
  • 208
  • 365