We recommend a series of articles about the right value. There are six articles in total.
Article 1: How fast is it? Pass the value
From: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
To be honest, how do you feel about the following code?
std::vector<std::string> get_names();…std::vector<std::string> const names = get_names();
Frankly speaking, although I know it's not that bad, I still feel bad. In principle, when get_names () returns, we must copy a vector containing multiple strings. Then, we need to copy the names again when initializing it, and finally we need to destroy the first copy. If there are n strings in the vector, each copy may require n + 1 memory allocation, and copying the string content will lead to a series of invalid cache data access.
To eliminate this concern, I usually use the reference transfer method to avoid useless replication:
get_names(std::vector<std::string>& out_param );…std::vector<std::string> names;get_names( names );
Unfortunately, this approach is not ideal.
- Code increased by 150%.
- We must remove const because we need to modify names.
- As programmers in functional programming often remind us, rewriting of function parameters can complicate the code because it damages the transparency of reference and equation reasoning.
- For names, we lose the strict value semantics.
Is it really necessary to write code in this way to improve efficiency? Fortunately, the answer is no need to (especially when you are using C ++ 0x ). We have a series of articles discussing the right value and its impact on improving the semantic efficiency of C ++ values. This is the first article in this series.
Right Value
The right value refers to the expression used to create an anonymous temporary object. The name of the right value comes from the fact that the built-in right value expression can only appear on the right of the value assignment operator. This is different from the left value. When no const is included, the left value can appear on the left of the value assignment operator, the object generated by the right expression does not have any persistent identifier to assign values to it.
However, another important feature of anonymous temporary objects is that they can be used only once in expressions. How can you mention such an object again? It has no name ("anonymous"), and after the entire expression is evaluated, the object is destroyed (that is, "temporary ")!
If you know that you are copying from the right value, you may "steal" resources with high replication overhead from the source object ", use them in the target object without anyone paying attention to it. In the previous example, the ownership of the dynamically allocated string array in the source vector is passed to the target vector. If we can let the compiler execute this "transfer" Operation for us to some extent, the cost of initializing names from the vector returned by passing the value is very low-almost zero.
The above is about the second copy. What about the first copy? In principle, when get_names returns, the return value of the function must be copied from the inside of the function to the outside. The returned values have the same features as anonymous temporary objects: they will be destroyed immediately and will not be used in the future. Therefore, we can use the same method to eliminate the first copy and transfer the resource from the return value inside the function to the anonymous temporary object visible to the function caller.
Copy omitted and RVO
As mentioned above, I wrote "in principle" because the compiler allows Optimization Based on the principles we have discussed. This type of optimization is usually called replication omitted. For example, in the return value optimization (RVO), the caller function allocates space on the stack, and then transmits the address of the memory to the called function. The called function can directly construct the return value on the memory to eliminate external replication from the inside of the function. This copy is omitted by the compiler, or is deleted ". Therefore, the following code does not require replication:
std::vector<std::string> names = get_names();
Similarly, when a function parameter is passed as a value, although the compiler is usually required to create a copy (So modifying this parameter within the function will not affect the caller ), however, when the source object is the right value, you can also omit this copy and directly use the source object itself.
std::vector<std::string> sorted(std::vector<std::string> names){ std::sort(names); return names;} // names is an lvalue; a copy is required so we don't modify namesstd::vector<std::string> sorted_names1 = sorted( names ); // get_names() is an rvalue expression; we can omit the copy!std::vector<std::string> sorted_names2 = sorted( get_names() );
This is really amazing. In principle, the compiler can eliminate all worrying copies in row 12th so that the objects created in sorted_names2 and get_names () are the same object. However, in practice, this principle will not go as far as we think. I will explain the reason later.
Inspiration
Although the replication omitted has never been implemented as required by the standard, the latest version of each compiler I have tested has implemented this optimization. Even if you are uncomfortable returning heavyweight objects by passing values, copying or skipping will change the way you write code.
Let's take a look at the following method of the previously set of sorted (...) functions. It accepts names passed in as a const reference and performs an explicit copy:
std::vector<std::string> sorted2(std::vector<std::string> const& names) // names passed by reference{ std::vector<std::string> r(names); // and explicitly copied std::sort(r); return r;}
Although sorted and sorted2 are the same at first glance, there will be huge performance differences if the compiler implements replication omitting. Even if the real parameter passed to sorted2 is the right value, the source object names for replication is also the left value, so the replication cannot be optimized. In a sense, replication Omitting is the victim of separation of the compilation mode: Inside the sorted2 function body, there is no information about whether the real parameter passed to the function is the right value; in the external call point, there is no indication that the real parameter will be copied.
This fact leads us directly to the following guidelines:
Guidelines: Do not copy your function parameters. Instead, it should be passed in the way of passing values, so that the compiler can perform replication.
In the worst case, if your compiler does not support replication omitting, the performance will not be worse. In the best case, you will see a significant performance improvement.
One thing you can immediately apply this guidance to is the value assignment operator. The normalized, easy-to-write, correct, and strongly exceptional value assignment operators that copy and exchange values are usually written as follows:
T& T::operator=(T const& x) // x is a reference to the source{ T tmp(x); // copy construction of tmp does the hard work swap(*this, tmp); // trade our resources for tmp's return *this; // our (old) resources get destroyed with tmp }
However, we can see from the above discussion that this writing method is obviously inefficient! Obviously, the correct assignment operation for copying and exchanging should be:
T& operator=(T x) // x is a copy of the source; hard work already done{ swap(*this, x); // trade our resources for x's return *this; // our (old) resources get destroyed with x}
Cannot be faked
Of course, there is no free lunch in the world, so I have the following instructions.
First, when you pass a parameter in reference mode and copy it in the function body, the copy constructor is called from a place in a set. However, when you pass a parameter by passing a value, the call to the copy constructor generated by the compiler is located at each call point that passes the left value. If the function is called in multiple places and the code size or locality is the key point of your application, this is indeed a problem.
On the other hand, you can easily create a packaging function that will replicate the local:
std::vector<std::string> sorted3(std::vector<std::string> const& names){ // copy is generated once, at the site of this call return sorted(names);}
Because the opposite is not true-you cannot use packaging to retrieve the opportunity for missing copies-so I suggest you start from the previous guidelines, then you can change it only when you find it is necessary.
Secondly, I have not found any compiler that can be omitted when the function returns its parameters, just as our sorted implementation. You can imagine how to make these copies omitted: without some form of cross-function optimization, the sorted caller cannot know its parameters (rather than other objects) and will eventually be returned, therefore, the compiler must allocate different spaces for parameters and return values on the stack.
If you want to return a function parameter, you can still obtain the approximate optimal performance, the method is to exchange with the return value of a default constructor (the provided default constructor and switching function must be of low overhead, and this is usually the case ):
std::vector<std::string> sorted(std::vector<std::string> names){ std::sort(names); std::vector<std::string> ret; swap(ret, names); return ret;}
Subsequent content
I hope that you will no longer feel anxious about passing in values or returning a non-trivial object. But we are not finished yet: we have discussed the right value, the omitted copy, and the RVO so far. We have the necessary background knowledge, you can continue to discuss the transfer semantics, right-value reference, perfect forwarding, and other content that will be continued in this series of articles. See you later!
Thanks
Howard hinnant is responsible for key insights that make this article series possible. andrei Alexandrescu was posting on comp. lang. c ++. moderated about how to leverage copy elision years before I took it seriously. most of all, though, thanks in general to all readers and reviewers!