Move Me, Please!
UPDATE: Don’t read this post. It’s wrong, sort of 🙂 Actually, please do read it and then go read John’s comment below along with my reply to him. D’oh!
Just because all the STL containers are “move enabled” in C++11, it doesn’t mean you get their increased performance for free when you use them as member variables in your own classes. You don’t. You still have to write your own “move” constructor and “move” assignment functions to obtain the superior performance provided by the STL containers. To illustrate my point, consider the code and associated console output for a typical run of that code below.
Please note the 3o-ish millisecond performance measurement we’ll address later in this post. Also note that since only the “move” operations are defined for the UserType class, if you attempt to “copy” construct or “copy” assign a UserType object, the compiler will barf on you (because in this case the compiler doesn’t auto-generate those member functions for you):
However, if you simply add “default” declarations of the copy ctor and copy assignment operations to the UserType class definition, the compiler will indeed generate their definitions for you and the above code will compile cleanly:
Ok, now remember that 30-ish millisecond performance metric we measured for “move” performance? Let’s compare that number to the performance of a “copy” construct plus “copy” assign pair of operations by adding this code to our main() function just before the return statement:
After compiling and running the code that now measures both “move” and “copy” performance, here’s the result of a typical run:
As expected, we measured a big boost in performance, 5X in this case, by using “moves” instead of old-school “copies“. Having to wrap our args with std::move() to signal the compiler that we want a “move” instead of “copy” was well worth the effort, no?
Summarizing the gist of this post; please don’t forget to write your own simple “move” operations if you want to leverage the “free” STL container “move” performance gains in the copyable classes you write that contain STL containers. 🙂
NOTE1: I ran the code in this post on my Win 8.1 Lenovo A-730 all-in-one desktop using GCC 4.8/MinGW and MSVC++13. I also ran it on the Coliru online compiler (GCC 4.8). All test runs yielded similar results. W00t!
NOTE2: When I first wrote the “move” member functions for the UserType class, I forgot to wrap other.vints with the overhead-free std::move() function. Thus, I was scratching my (bald) head for a while because I didn’t know why I wasn’t getting any performance boost over the standard copy operations. D’oh!
NOTE3: If you want to copy and paste the code from here into your editor and explore this very moving topic for yourself, here is the non-.png listing:
#include <iostream> #include <chrono> #include <vector> class UserType { public: // 100 million ints with value 100 UserType() : vints(100000000, 100) { } //move ctor UserType(UserType&& other) { //invoke std::vector move assignment vints = std::move(other.vints); } //move assignment UserType& operator=(UserType&& other) { //invoke std::vector move assignment vints = std::move(other.vints); return *this; } UserType(const UserType& other) = default; UserType& operator=(const UserType& other) = default; private: std::vector<int> vints; }; int main() { UserType ut1{}; UserType ut2{}; using namespace std; using namespace std::chrono; auto start = steady_clock::now(); ut2 = std::move(ut1); UserType ut3{std::move(ut2)}; auto end = steady_clock::now(); cout << "Time for move ctor + move assignment (micros) = " << duration_cast<microseconds>((end-start)).count() << endl; UserType ut4{}; UserType ut5{}; start = steady_clock::now(); ut5 = ut4; UserType ut6{ut5}; end = steady_clock::now(); cout << "Time for copy ctor + copy assignment (micros) = " << duration_cast<microseconds>((end-start)).count() << endl; return 0; }
Great post! Good info….
Your example UserType does not need to declare the move constructor/assignment operator. As long as you don’t declare a destructor or copy constructor/assignment operator, and your data members are all moveable, you will get the implicit move semantics.
Sorrry, but I think you are wrong about the last part. The compiler generated move operations will implement copies. I’ll test it out and update.
Nope! You are right! UserType needs no explicitly defined move/copy operations. As long as the caller uses std::move(), the std::vector moves will get executed. Thanks for straightening me out.
Running the above code on Coliru yields:
Time for move ctor + move assignment (micros) = 16139
Time for copy ctor + copy assignment (micros) = 97018