4

Basing on what I've gathered from compiler writers value types are much preferred to references/pointers in terms of efficiency.

This comes from a fact that a values types are easier to reason about when you don't have to care about aliasing, externally changed memory (which the pointer refers to), cost of pointer dereference, and such things. I have to say that while I understand such concerns I still have a few questions regarding specific cases.

Case #0

void foo(const float& f) Okay, we have a reference here, but it's constant! Sure we have a constant view (ref) of it, so externally it might be change, but it could only happen in multithreaded world and I am not sure if compiler has to take it into consideration at all if there are no synchronization primitives used. Obviously if internally we used another pointer/reference to any float variable we might be at risk of modifying the f parameter. Can compiler treat this parameter as safe (assuming we don't use any ref/ptr to float internally)?

Case #1

void foo(vector<int> f) Talking from a C++11/14 programmer perspective I know that the vector can be safely moved into the function. As we all know, internally the container holds a pointer to an array. Will the compiler treat the pointer as safe (no externally modifications) as we just got a copy of vector, so we are the only owners of it?

In other words: is a copied object treated as a safe one (because logically we make a clone of the object), or the compiler is not allowed to make such assumptions and any ptr/ref member has to be treated as potentially dangerous as the copy ctor/op might not have made a proper copy. Shouldn't the programmer be responsible for handling shared resources when copying them?

Bottomline:

Do constant pointers/references and copied complex objects are generally slower than copies of primitives, and thus should be avoided as much as possible in performance critical code; or they are only slightly less efficient and we shouldn't fret about it?

Red XIII
  • 5,771
  • 4
  • 28
  • 29
  • 2
    I'm not so sure about this question... "Do constant pointers and copied objects are slower than copies of primitives"...? Those are all different things that solve different problems. What exactly are you trying to compare, and which problem do you need to solve? – Kerrek SB Aug 01 '13 at 08:55
  • 1
    Vote to close, premature optimization=>not a specific case=>too broad. – MSalters Aug 01 '13 at 09:32

3 Answers3

3

As general rules in modern C++:

  1. For (cheap to copy) primitive types, like int, float or double, etc., if it's an input (read-only) parameter, just pass by value:

    inline double Square(double x)
    {
        return x*x;
    }
    
  2. Instead, if the type of the input parameter is not cheap to copy, but it's cheap to move, e.g. std::vector, std::string, etc., consider two sub-cases:

    a. If the function is just observing the value, then pass by const reference (this will prevent useless potentially expensive deep-copies):

    double Average(const std::vector<double> & v)
    {
        .... // compute average of elements in v (which is read-only)
    }
    

    b. If the function is taking a local copy of the parameter, then pass by value, and std::move from the value (this will allow optimizations thanks to move semantics):

    void Person::SetName(std::string name)
    {
        m_name = std::move(name);
    }
    
Mr.C64
  • 41,637
  • 14
  • 86
  • 162
  • 2
    uh, bad example. You should use `v` instead of moving it into `w`. – user541686 Aug 01 '13 at 08:41
  • on a side note, for maximum convinience and performance you can define two overloaded functions which one which takes `void f(const float&);` and one that passes by value `void f(float);` – mewa Aug 01 '13 at 08:44
  • The question is not asking about efficience in passing the parameters, which I think OP takes for granted, but in using them. More specifically, about oportunities for optimisations performed by compilers. – Gorpik Aug 01 '13 at 08:48
  • @KerrekSB: What you wrote is silly. `m_name` is a data member of `Person` class. – Mr.C64 Aug 01 '13 at 09:01
  • @Mr.C64: OK, fair enough :-S – Kerrek SB Aug 01 '13 at 09:03
  • Uh, why would you want to move from functions parameter? The point of taking parameter by value is that `name` gets move constructed if you pass it a temporary. – jrok Aug 01 '13 at 09:10
  • @jork: And then it gets _moved_ again in `m_name `, instead of doing (useless) deep-copies. – Mr.C64 Aug 01 '13 at 09:12
  • Ah, it's a setter. I get it now, sorry :) – jrok Aug 01 '13 at 09:14
  • @jrok: In other words, if you don't move from parameter, you get _move_ + _copy_, instead in my code I get _move_ + _move_, which is more efficient than move+copy. – Mr.C64 Aug 01 '13 at 09:14
2

(Started as a comment but it wouldn't fit.)

Case #0 has already been discussed to death, for example:

Is it counter-productive to pass primitive types by reference?

which is already a duplicate of two other questions. In particular, I find this answer a good answer to your case #0 as well. Related questions:

Case #1 is unclear to me: Do you need a copy or do you want to move? There is an enormous difference between the two and it is unclear from what you write which one you need.

If a reference suffices but you do a copy instead, you are wasting resources.

If you need to make a deep copy then that's all there is to it, neither references nor moving will help.

Please read this answer and revise case #1.

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
1

Case #0

No - It may be externally modified:

void foo(const float& f) {
 ..use f..
 ..call a function which is not pure..
 ..use (and reload) f..
}

Case #1 ... Will the compiler treat the pointer as safe (no externally modifications) as we just got a copy of vector, so we are the only owners of it?

No - it must be pessimistic. It could be taught to rely on an implementation but in general, it has no reasonable way of tracking that pointer through all possible construction scenarios for arbitrary construction to verify it is safe, even if the implementations were visible.

Bottomline:

Cost of allocation and copying containers tend to be much greater than the cost of the loads and stores -- depends on your program, hardware, and implementation!

Passing small objects and builtins by reference doesn't mean an optimizer must treat it as a reference when the implementation is visible. E.g. If it sees the caller is passing a constant, it has the liberty to make the optimization based on the known constant value. Conversely, creating a copy can interfere with the ability to optimize your program since complexity can increase. Fretting over whether or not to pass this trivial/small type by value is an old micro-optimization. Copying a (non-SSO) string or vector OTOH can be huge in comparison. Focus on the semantics first.

I write tons of performance critical code and pass almost everything by (appropriately const-qualified) reference -- including builtins. You're counting instructions and speed of memory at that point (for your parameters), which is very low in desktop and portable computers. I did plenty of testing on desktops and notebooks before settling on that. I do that for uniformness - you don't need to worry about the cost of introducing the reference (where overhead exists) outside embedded. Again, the cost to make unnecessary copies and any necessary dynamic allocations tend to be far greater. Also consider that objects have additional construction, copy, and destruction functions to execute -- even innocent looking types can cost much more to copy than to reference.

justin
  • 104,054
  • 14
  • 179
  • 226