C ++ 0x, rvalue reference, move semantics, RVO, NRVO-what do we need?

Source: Internet
Author: User

 

Visual c ++ 2010 (VC10) implements some useful new C ++ 0x features, including rvalue reference
.

This article does not describe what rvalue reference is. There are already many articles about this. You can search for them by yourself. What I want to say is that today I have made some very simple
Rvalue reference performance testing, which has a very encouraging part, as well as a consistent and increasingly complex part of C ++.

Good news: performance is greatly improved

In principle, Rvalue reference makes the move Semantics
This makes it possible for the compiler to "steal" resources from the rvalue object rather than copy data. In many cases, this will greatly improve the performance.

The test code is simple: Compare the time of copying and moving a vector <string> object:

# Include <string> <br/> # include <vector> <br/> # include <iostream> <br/> # include <ctime> <br/> using namespace STD; <br/> vector <string> make_vector () <br/>{< br/> vector <string> V (1, string ("this is a string ")); <br/> return V; <br/>}< br/> int main () <br/>{< br/> vector <string> SRC (make_vector ()), S (make_vector (); <br/> clock_t start, end; <br/> Start = clock (); <br/> for (INT I = 0; I <1000000; ++ I) <br/>{< br/> vector <string> S = SRC; <br/>}< br/> end = clock (); <br/> cout <"vector copy ctor takes:" <End-start <Endl; <br/> Start = clock (); <br/> for (INT I = 0; I <1000000; ++ I) <br/>{< br/> vector <string> S = move (SRC ); <br/>}< br/> end = clock (); <br/> cout <"vector move ctor takes:" <End-start <Endl; <br/> return 0; <br/>}< br/> 

In my fairly old laptop, the input for the release version is like this.

 

Vector copy ctor takes: 4562
Vector move ctor
Takes: 4


It seems quite encouraging, isn't it? For an object that is not too big or too complex, moving can improve the performance by thousands of times than copying! If you increase the vector size, move
The execution time of the copy version is not much different, and the execution time of the copy version is extended as the object increases. When we write a function to construct an object, we no longer need to use ugly similar

 

Void make_vector (vector <string> & out) 


To avoid object copying, we only need to add move to the return point or call point!

Well, if you are careful enough, you will find that the above Code is playing a trick: it does not call make_vector in a loop, it just saves the value, and then uses copy and move
. The first reason is that if make_vector is called in a loop, most of the time we have measured is being constructed. copy and move
The differences between them cannot be displayed. The second reason is described later.

If you want to see the result of calling make_vector in a loop

Vector <string> s = src;

And

Vector <string> s =
Move (src );

Replace

Vector <string> s =
Make_vector ();

And

Vector <string> s =
Move (make_vector ());

The running result here is as follows:

 

Vector copy ctor takes: 7928
Vector move ctor
Takes: 3587


Obviously, the execution time of multiple loops is roughly the same, that is, the time for constructing objects.

In C ++, there are exceptions in everything

If we replace the test object with a string, the results will be similar when the string size is relatively large. For example, the following program tests the copy and move sizes of 20
String:

 

# Include <string> <br/> # include <iostream> <br/> # include <ctime> <br/> using namespace std; <br/> int main () <br/>{< br/> string src (20, 'E'); <br/> clock_t start, end; <br/> start = clock (); <br/> for (int I = 0; I <1000000; ++ I) <br/>{< br/> string s = src; <br/>}< br/> end = clock (); <br/> cout <"String copy ctor takes:" <end-start <endl; <br/> start = clock (); <br/> for (int I = 0; I <1000000; ++ I) <br/>{< br/> string s = move (src); <br/>}< br/> end = clock (); <br/> cout <"String move ctor takes:" <end-start <endl; <br/> return 0; <br/>} 

Here, the output is similar

 

String copy ctor takes: 1728
String move ctor
Takes: 40


Since copying strings is a fast operation, the gap is not that big, but it is still quite obvious.

Here, you will say, "well, I will make rvalue reference and move all over the sky in the Code"

However, if you reduce the size of the string src by a little, the output will be similar

 

String copy ctor takes: 40

String move ctor
Takes: 42


Why is copying a 15-character string much faster than copying 20 characters? Read the string class and you will find that the string class will pre-allocate 16 bytes to itself
Buffer. If the object to be copied cannot exceed 15 characters, you do not need to allocate space again. You only need to call memcpy. This is a very efficient operation. Move
In this case, memmove is called instead of pointer exchange, which is usually slower than memcpy.

What conclusions do we come? There are several

  1. Copying, especially copying a small amount of data, is actually very efficient.
  2. Dynamic memory allocation is very expensive. From the above results, we can roughly infer that it takes about 40 times more time to allocate a space than to copy 20 bytes.
  3. The copy of a small string (less than 15 characters) is optimized enough.

I have not planned to stop it here. If it is so simple, it will not be C ++. If you carefully examine the object copy and move, the process will be more complicated.

Returned values and RVO

Write a very simple class Foo, which helps us understand what happened between copy and move.

# Include <iostream> <br/> using namespace std; <br/> struct Foo <br/> {<br/> Foo () {cout <"Foo ctor" <endl ;}< br/> Foo (const Foo &) {cout <"Foo copy ctor" <endl ;} <br/> void operator = (const Foo &) {cout <"Foo operator =" <endl ;}< br/> Foo (Foo &&) {cout <"Foo move ctor" <endl ;}< br/> ~ Foo () {cout <"Foo dtor" <endl ;}< br/> void bar () {}< br/>}; <br/> Foo make_foo () <br/>{< br/> return Foo (); <br/>}< br/> int main () <br/>{< br/> cout <"Copy from rvalue:" <endl; <br/> Foo f1 = make_foo (); <br/> cout <"---------------------" <endl; <br/> cout <"Move from rvalue:" <endl; <br/> Foo f2 = move (make_foo (); <br/> cout <"---------------------" <endl; <br/> return 0; <br/>} 

What is output?

 

Copy from rvalue:
Foo ctor

-----------------------
Move from rvalue:
Foo ctor
Foo move ctor

Foo dtor
-----------------------
Foo dtor
Foo dtor


What's going on? When we copy, we only call one constructor, and even do not call copy constructor.
You need to call a constructor, a move constructor, and a destructor.

Move is easy to understand and can be divided into three steps:

  1. Call constructor to construct a temporary object
  2. Move constructing from this temporary object
  3. Destroy this temporary object

Why is copy so easy? Because the compiler uses RVO (return value optimization ),
This object is directly constructed in the object space that receives the returned value, thus reducing the copy. On the contrary, moving will impede the compiler from RVO, but adds two function calls. If
The destructor involves the release of dynamic space and some time-consuming operations, but it is impossible to hack the rice.

What conclusions do we get?

  1. RVO is a good thing.
  2. Moving semantics may not be faster when there is RVO

I have no plans to stop it:

Nrvo

If you change the make_foo function to the following format:

Foo make_foo () <br/>{< br/> Foo f; <br/> return f; <br/>} 

After running in Debug mode, the results are even more interesting:

 

Copy from rvalue:
Foo ctor
Foo move ctor

Foo dtor
-----------------------
Move from rvalue:
Foo ctor

Foo move ctor
Foo dtor
Foo move ctor
Foo dtor

-----------------------
Foo dtor
Foo dtor


Why? Why did we call the move constructor when we explicitly intend to copy it, but we call the move constructor.
? We will analyze them one by one.

Copy

First, the Foo f in make_foo function will generate a constructor regardless of copy or move.

Here f is an lvalue, so during copy, the compiler cannot perform RVO on it, and other Optimizations in Debug mode are disabled, so we have to use the return value to construct the object.
F1.

Here, something new appears: the new C ++ standard requirements, when constructing the returned temporary object, if you do not use RVO, and the class defines move
Constructor,Move constructor is preferred. So we can see the move constructor
Call is used to initialize a temporary object.

With this temporary object, the compiler can directly throw it to f1 to save a copy from the temporary object to f1.

Then, the local object f is out of the valid range and is destroyed.

Move

Construct object f.

Like copy, use move constructor to construct a temporary object.

Here's the problem: adding the move () call makes it impossible for the compiler to optimize the copy of the temporary object to f2. Therefore, the compiler goes back to the next step and uses the move constructor to initialize
F2.

Partial object f is destroyed.

The temporary object is destroyed.

Okay. We can see that move () has another cup. What if it is in the Release mode? The result is as follows:

 

Copy from rvalue:
Foo
Ctor
-----------------------
Move from rvalue:
Foo ctor
Foo move
Ctor
Foo dtor
-----------------------
Foo dtor
Foo dtor


Here, both copy and move reduce the generation of an object. Which one is it? The answer is a temporary object. This is due to the NRVO (Named Return Value) of the compiler.
Optimization). This Optimization allows the compiler to reduce the copy (or move) of an object when returning an lvalue, but this does not optimize
.

Conclusion

Rvalue reference and move semantics are both good things, and std: move () are also good things. However, improper use may be counterproductive.

In fact, after moving semantics, the most efficient response is the form we are familiar:

Foo make_foo () <br/>{< br/> Foo f; <br/> return f; <br/>}< br/> ...... <Br/> Foo f1 = make_foo (); 

Because the compiler tries its best to use RVO and NRVO, and when these optimizations are not available, the compiler will try its best to call move because make_foo returns an rvalue.
Constructor, and only when these all fail, the compiler will take the copy constructor we are familiar with-in short, it will not be worse than this.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.