Archive

Posts Tagged ‘std::move’

The Move That Wasn’t

June 30, 2015 3 comments

While trying to duplicate the results I measured in my “Time To Get Moving!” post, an astute viewer posted this  comment:

move comment

For your convenience, I re-list the code in question here for your inspection:


#include <iostream>
#include <vector>
#include <utility>
#include <chrono>
using namespace std;

struct Msg{
  vector<double> vDoubs;
  Msg(const int NUM_ELS) : vDoubs(NUM_ELS){
  }
};

int main() {
//Construct a big Msg object!
const int NUM_ELS = 10000000;
Msg msg1{NUM_ELS};

//reduce subsequent code verbosity
using std::chrono::steady_clock;
using std::chrono::duration_cast;
using std::chrono::microseconds;

//Measure the performance of
//the "free" copy operations
auto tStart = steady_clock::now();
Msg msg2{msg1}; //copy ctor
msg1 = msg2; //copy assignment
auto tElapsed = steady_clock::now() - tStart;
cout << "Copy ops took "
     << duration_cast<microseconds>(tElapsed).count()
     << " microseconds\n";

//Measure the performance of
//the "free" move operations
tStart = steady_clock::now();
Msg msg3{std::move(msg1)}; //move ctor
msg1 = std::move(msg3); //move assignment
tElapsed = steady_clock::now() - tStart;
cout << "Move ops took "
     << duration_cast<microseconds>(tElapsed).count()
     << " microseconds\n";

cout << "Size of moved-from object = "
     << msg3.vDoubs.size();
} //"free" dtor is executed here for msg1, msg2, msg3

Sure enough, I duplicated what my friend Gyula discovered about the “move” behavior of the VS2013 C++ compiler:

VS2013 move

move response

Intrigued by the finding, I dove deeper into the anomaly and dug up these statements in the fourth edition of Bjarne Stroustrup’s “The C++ Programming Language“:

default ops

Since the Msg structure in the code listing does not have any copy or move operations manually defined within its definition, the compiler is required to generate them by default. However, since Bjarne doesn’t state that a compiler is required to execute moves when the programmer explicitly directs it to with std::move() expressions, maybe the compiler isn’t required to do so. However, common sense dictates that it should. Hopefully, the next update to the VS2013 compiler will do the right thing – like GCC (and Clang?) currently does.

Move Me, Please!

February 10, 2014 4 comments

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.

UserType Move

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):

UserType No Copy

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:

UserType Copy

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:

UserType Copy

After compiling and running the code that now measures both “move” and “copy” performance, here’s the result of a typical run:

Move vs Copy Perf

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;
}
Categories: C++11 Tags: ,

No Runtime Overhead

October 15, 2013 15 comments

Since I have the privilege of using C++11/14 on my current project, I’ve been using the new language idioms as fast as I can discover and learn them. For example, instead of writing risky, exception-unsafe, naked “new“, code like this:

nakedptr

I’ve been writing code like this instead:

stdmove1

By using std::unique_ptr instead of a naked pointer, I don’t have to veer away from the local code I’m writing to write matching delete statements in destructors or in catch() exception clauses to prevent inadvertent memory leaks.

I could’ve used a std::shared_ptr (which can be copied instead of “moved“) in place of the std::unique_ptr, but std::shared_ptr is required to maintain a fatter internal state in the form of strong and weak owner counters. Unless I really need shared ownership of a dynamically allocated object, which I haven’t so far, I stick to the slimmer and more performant std::unique_ptr.

sharedunique

When I first wrote the std::unique_ptr code above, I was concerned that using the std::move() function to transfer encapsulated memory ownership into the safeTgtList vector would add some runtime overhead to the code (relative to the C++98/03 style of simply copying the naked pointer into the scaryTgtList vector). It is, after all, a function, so I thought it must insert some code into my own code.

compilerinserted

However, after digging a little deeper into my concern, I discovered (via Stroustrup, Sutter, and Meyers) that std::move() adds zero runtime overhead to the code. Its use is equivalent to performing a static_cast on its argument – which is evaluated at compile time.

staticcast

As Scott Meyers stated at GoingNative13, std::move() doesn’t really move anything. It simply prepares for a subsequent real move by casting its argument from an lvalue to an rvalue – which is required for movement of an object’s innards. In the previous code, the move is actually performed within the std::vector::emplace_back() function.

Quoting Scott Meyers: “think of std::move() as an rvalue_cast“. I’m not sure why the ISO C++ committee didn’t define a new rvalue_cast keyword instead of std::move() to drive home the point that no runtime overhead is imposed, but I’d speculate that the issue was debated. Perhaps they thought rvalue_cast was too technical a term for most users?

Update 10/25/13

As I said early in the post, the code example is “like” the code I’ve been writing. The real code that triggered this post is as shown here:

Real Code

Since each “entryCfarDetState object must be uniquely intialized form it’s associated CfarCrossing object, I can’t simply insert estd::make_unique<CfarDetState>() into the emplace_back() function. All of the members  of each “entry” must be initialized first. Regardless of whether I use emplace_back() or push_back(), std::move(entry) must be used as the argument of the chosen function.

My C++11 “Move” Notes

February 12, 2013 Leave a comment

Being a slow learner, BD00 finds it easier to learn a new topic by researching the writings from multiple sources of authority. BD00 then integrates his findings into his bug-riddled brain as “the truth as he sees it” (not the absolute truth – which is unknowable). The different viewpoints offered up by several experts on a subject tend to fill in holes of understanding that would otherwise go unaddressed. Thus, since BD00 wanted to learn more about how C++11’s new “move” and  “rvalue reference” features work, he gathered several snippets on the subject and hoisted them here.

Why Move?

The motivation for adding “rvalue” references and “move” semantics to C++11 to complement its native “copy” semantics was to dramatically improve performance when large amounts of heap-based data need to be transferred from one class object to another AND it is known that preserving the “from” class object’s data is unnecessary (e.g. returning a non-static local object from a function). Rather than laboriously copying each of a million objects from one object to another, one can now simply “move” them.

Unlike its state after a “copy“, a moved-from object’s data is no longer present for further use downstream in the program. It’s like when I give you my phone. I don’t make a copy of it and hand it over to you. After I “move” it to you, I’m sh*t outta luck if I want to call my shrink – until I get a new phone.

Chapter 13 – C++ Primary, Fifth Edition, Lippman, Lajoie, Moo

Cpp Primer Move

Chapter 3 – The C++ Programming Language, 4th Edition, Bjarne Stroustrup

My Move Notes

Chapter 3 – The C++ Standard Library, 2nd Edition, Nicolai M. Josuttis

Nicolai Move

Overview Of The New C++ (C++11), Scott Meyers

Meyers Cpp11 Move

%d bloggers like this: