Does std::move helps with objects on stack?

Viewed 209

I just saw a college doing this optimization today:

enter image description here

i.e., change this:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;

std::string myfunction( const std::string& mystring, int myint )
{
    myglobalmap.insert( std::pair<std::string, int>( mystring, myint ) );
}
myfunction(somestring, 10)

To:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;

std::string myfunction( std::string mystring, int myint )
{
    myglobalmap[ std::move(mystring) ] = myint;
}
myfunction(somestring, 10)

He claimed that passing the value mystring by copy instead of passing it as a constant reference would be faster because the move operation would be only performed with objects on the stack. But I could not see why this would help. I searched about the move operator and I found out it does not move anything, it just makes my expression to return a reference.

Then, if this is true, passing the value by copy would not be slower than passing it by reference and calling std::move or does calling std::move help with objects on the stack in this case? If I understand correctly, std::move should only help with objects on the heap. Then, calling it with something on the stack should not help or it does?

Related questions:

  1. What is std::move(), and when should it be used?
  2. Is it possible to std::move local stack variables?
3 Answers

and I found out it does not move anything, it just makes my expression to return a reference.

This is correct. Crucially, the result of the function is an xvalue.

Then, if this is true, passing the value by copy would not be slower than passing it by reference and calling std::move or does calling std::move

In case of std::string (assuming the contained string isn't trivially small), a copy and a move is slower than indirection through a reference and move.


In your first example, you indirect through a reference and always make a copy.

In the second example, you copy only if the function argument is an lvalue. Second is faster than the fist when the argument is an rvalue (as long as the stored string is long enough for the difference to be significant).


Both versions have undefined behaviour because the functions are declared to return non-void, but do not return a value.

First, I'd like to say that this changes the behavior of myfunction. The first version does not insert the integer if the key already exists in the map. The second version replaces the integer with the new value.

That said, In the case where the passed string is not already in the map, and hence it would have made a copy, this could be slightly more efficient. If myfunction is passed a temporary, the compiler could move construct (or even optimize the move out), and that would then be moved into the map. While std::move doesn't move anything, using it causes the map::operator[] to use the rvalue reference overload, which can in turn invoke the move constructor on std::string.

However, in cases where the key already exists, it might cause the creation of an extra copy that's not needed.

I had missed an important point in the question, there was a lock right before myglobalmap.insert( and the new myglobalmap[.

i.e., the change was this:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;
std::mutex g_pages_mutex;

std::string myfunction( const std::string& mystring, int myint )
{
    std::lock_guard<std::mutex> guard(g_pages_mutex);
    myglobalmap.insert( std::pair<std::string, int>( mystring, myint ) );
    return "";
}
myfunction(somestring, 10)

To:

std::string somestring =  "somestring";
std::map<std::string, int> myglobalmap;
std::mutex g_pages_mutex;

std::string myfunction( std::string mystring, int myint )
{
    std::lock_guard<std::mutex> guard(g_pages_mutex);
    myglobalmap[ std::move(mystring) ] = myint;
    return "";
}
myfunction(somestring, 10)

So in extension to the answer provided by @eerorika, the optimization performed was to call the copy constructor before getting the lock. Meaning the second version should be faster as the copy is performed without holding a lock.

Related