3

this code

#include <iostream>
#include <vector>

struct obj
{
  std::string name;
  int age;
  float money;

  obj():name("NO_NAME"),age(0),money(0.0f){}
  obj(const std::string& _name, const int& _age, const float& _money):name(_name),age(_age),money(_money){}

  obj(obj&& tmp): name(tmp.name), age(tmp.age), money(tmp.money) {}
  obj& operator=(obj&&) {return *this;}

};

int main(int argc, char* argv[])
{
  std::vector<obj> v;
  for( int i = 0; i < 5000000; ++i )
  {
    v.emplace_back(obj("Jon", 45, 500.6f));
  }
  return(0);
}

is about 2 time slower than the equivalent with v.push_back(obj("Jon", 45, 500.6f)); and I don't get why.

I have tested this with bot g++ 4.7.2 and clang++ 3.3.

Where I'm wrong ?


now that i have corrected my move construnctor i will add more

this is a push_back version

this is the emplace_back version

I'm testing this 2 with the time utility under Linux and compiling them with

g++-4.7 -std=c++11 -s -O3 -DNDEBUG

or

clang++ -std=c++11 -s -O3 -DNDEBUG
user1797612
  • 812
  • 6
  • 22
  • 2
    Well, well... If you want to move... shouldn't you try actually moving? The move constructor does not move anything; it copies everything (hint: there's no call to `std::move` anywhere). FYI the compiler generated move ctor will do the right thing. – R. Martinho Fernandes Nov 20 '12 at 13:09
  • @R.MartinhoFernandes it's not implicit the fact that "move semantic" should "move" things ... ? – user1797612 Nov 20 '12 at 13:12
  • Only rvalues get moved implicitly. All the things in the member initialization list of the move constructor have names, and thus are lvalues. I guess the confusing bit is that you were not aware that *named rvalue references are lvalues*. – R. Martinho Fernandes Nov 20 '12 at 13:12
  • @R.MartinhoFernandes mmm, ok, now the only thing that i got is that with this std::move my move constructor is highly inefficient, it still doesn't beat the push_back, how to improve this ? – user1797612 Nov 20 '12 at 13:15
  • In the linked versions I get 1.61 on `push_back` and 1.39 on `emplace_back`. – ronag Nov 20 '12 at 13:28
  • @ronag i can't get an emplace_back faster than a push_back on my machine ... hwo this can be ? – user1797612 Nov 20 '12 at 13:30
  • The difference between a move an an emplace is trivial in this case because the time is dominated by the initial string constructor from the string literal, which must be present in both cases. – Andrew Tomazos Nov 20 '12 at 13:45
  • @AndrewTomazos-Fathomling this is because a string like this is a const pointer and can't be moved ? – user1797612 Nov 20 '12 at 13:50
  • @ronag now i have tried with g++ 4.6 and with this version i got an emplace_back faster than a push_back, probably because of this http://stackoverflow.com/questions/13473815/c11-move-semantics-is-slow-on-construction#comment18433348_13474304 – user1797612 Nov 20 '12 at 13:52
  • A string literal is placed in the read-only (.rodata) section of the application image. When a std::string is constructed from one it must allocate space for it, calculate its size and then take a copy of it. This is a typical design decision (not sure if standard mandated) so that dynamic std::strings and ones constructed from string literals can be uniformly treated. – Andrew Tomazos Nov 20 '12 at 13:55

5 Answers5

5

Not doing anything is better. You tried to make it faster (faster than what? did you actually profile before you wrote the move constructor?), but you broke it.

The compiler generates copy and move constructors and assignment operators for free, and she does it right. By deciding to write your own, you are telling the compiler that you know better, so she just gets out of the way and lets you improve break it on your own.

The first thing you broke, is that you made your move constructor actually copy. Things with a name are lvalues, and lvalues cannot be moved implicitly even if they are rvalue references. So the initializers need to actually call std::move.

The second thing you broke is that you didn't make the move constructor declare that it does not throw by adding noexcept to it. The compiler generated one had this. By not declaring that no exceptions are thrown, the implementation of std::vector will probably not use moves when reallocating the underlying storage: it cannot provide the strong exception guarantee without the assurance that moves don't throw.

Will doing all this make it perform better? Maybe. Maybe not. Your implementation may be doing the small string optimization on std::string, and that means that there is no dynamic allocation: the whole string "Jon", being small, will be stored directly in the std::string object. This makes a move have the same costs as a copy.

You can make the whole obj structure take advantage of a cheap move by allocating it dynamically, and using unique_ptr. This will make moves cheaper than copies, even in the presence of the small string optimization. However, you are paying for that cheapness with the cost of allocation and extra indirection. Whether that is desirable or not only you can tell.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Though this has nothing to do with why a proper `emplace_back` call would be slower than `push_back`, since the move constructor is never called with a proper `emplace_back` invocation. – ronag Nov 20 '12 at 13:32
  • I want to make it faster than a push_back. but you know what?!, g++4.7.2 generates a slower code than g++ 4.6, and with g++ 4.6 emplace_back is faster than a push_back, i will add a noexcept but for what i see there is something wrong with g++ 4.7.2 – user1797612 Nov 20 '12 at 13:36
  • @user That's most likely because g++4.6 does not provide the strong exception guarantee if the move throws (g++4.6 did not have noexcept, IIRC). The only things wrong here are g++4.6 and the code in question. – R. Martinho Fernandes Nov 20 '12 at 13:41
  • @R.MartinhoFernandes so the noexcept keyword will be inifluent in g++4.6 and essential in g++4.7 ? – user1797612 Nov 20 '12 at 13:49
  • @user yes. I remember there was a time when noexcept was available but almost worthless, because the standard library did not make use of it. Now it does, and that makes it crucial (I must admit I am still not yet used to that fact and sometimes do forget it). – R. Martinho Fernandes Nov 20 '12 at 13:52
  • @R.MartinhoFernandes in the end i have modified the signatures of my construnctors and operator like this `obj& operator=(obj other) noexcept{...}` and i suppose that this is what you are suggesting – user1797612 Nov 20 '12 at 14:04
  • 1
    Well, actually I was suggesting not defining them at all, or defining them as `= default;`. It's safer to let the compiler do it right. – R. Martinho Fernandes Nov 20 '12 at 14:07
4

You should move the data from the argument to the move constructor:

obj(obj&& tmp)
: 
name(std::move(tmp.name)), age(std::move(tmp.age)), money(std::move(tmp.money)) {}

Although this should be irrelevant if you use emplace_back correctly.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • Why would this make a difference to a proper `emplace_back` call? Isn't the object constructed "in-place", I would more look at moving the parameters sent to `obj(const std::string& _name, const int& _age, const float& _money)` (see my answer), which I think is the only method invoked by a proper `emplace_back` invocation. – ronag Nov 20 '12 at 13:23
  • @ronag I disn't see the emplace_back. Of course, it would make no difference whatsoever! – juanchopanza Nov 20 '12 at 13:25
  • what do you mean with " if you use emplace_back correctly.", what can be improved in the use of emplace_back in this code ? I get that i have wroted the move constructor in the wrong way, but what about the usage ? – user1797612 Nov 20 '12 at 14:06
  • @user1797612 use `v.emplace_back("Jon", 45, 500.6f);`, as has been pointed out in another post. `emplace_back` constructs an object from the arguments. The class' move constructor doesn't come into it (unless the vector gets resized as a result of the emplace_back). – juanchopanza Nov 20 '12 at 14:08
  • @juanchopanza but this is correct `v.emplace_back(obj("Jon", 45, 500.6f));` – user1797612 Nov 20 '12 at 14:10
  • @user1797612 No it isn't "correct". It isn't incorrect either, but it is not the intended use of `emplace_back`. See [here](http://en.cppreference.com/w/cpp/container/vector/emplace_back). – juanchopanza Nov 20 '12 at 14:11
  • @juanchopanza usually when people talk like that it's because you have just wroted a piece of code that behaves like another and it's basically useless for what it's meant to be. I think that in my case I don't have objects "already well formed" to move, so i need to construct them, but i can't construct them without copying them first because they are const, or at least the string is const, so it's not movable and only copiable in the first, after the construction they will be movable. – user1797612 Nov 20 '12 at 14:33
  • Since the scope resolution is capable of calling the right construnctor (copying or moving, old semantic vs new move semantic) I don't "see" the error that you are outlining. It's this the "error" ? – user1797612 Nov 20 '12 at 14:34
2

Instead of

v.emplace_back(obj("Jon", 45, 500.6f));

try

v.emplace_back("Jon", 45, 500.6f);

push_back has a move-enabled overload. emplace_back is for constructing in-place.

Edit: What R. Martinho Fernandes said. :)

obj(obj&& tmp): name(std::move(tmp.name)), age(std::move(tmp.age)), money(std::move(tmp.money)) {}
moswald
  • 11,491
  • 7
  • 52
  • 78
  • I have already tried this, it's still slower than a push_back, much slower. – user1797612 Nov 20 '12 at 13:11
  • 3
    @moswald you also need to add noexcept on the move constructor, otherwise gcc std::vector won't move anything at all (on insertion or during reallocation) – Arzar Nov 20 '12 at 13:15
  • To add to what Thomas said, the compiler generated default will, once more, do the right thing. – R. Martinho Fernandes Nov 20 '12 at 13:17
  • i have corrected my move constructor but everything it's just slower than a push_back – user1797612 Nov 20 '12 at 13:18
  • @ThomasPetit: I did not know that. Is that a gcc idiosyncrasy, or is that in the standard somewhere? – moswald Nov 20 '12 at 15:56
  • @R.MartinhoFernandes: for some reason I thought the move constructor wasn't automatically generated. Am I wrong on this? – moswald Nov 20 '12 at 15:56
  • 1
    @moswald it is unless you declared one, or unless you declare a copy constructor. Or unless you are using MSVC10. – R. Martinho Fernandes Nov 20 '12 at 16:38
  • @R.MartinhoFernandes Ah, it was the part about the copy constructor I was thinking of. Thanks. – moswald Nov 20 '12 at 16:57
  • 1
    @moswald for completeness, in that case, you may want to consider declaring the move constructor as `= default;` and have the compiler generate it anyway. Assuming the default semantics are desireable, of course. – R. Martinho Fernandes Nov 20 '12 at 17:05
  • 1
    @moswald forced by the standard I think. 23.2.1.10 "if an exception is thrown by a push_back() or push_front() function, that function has no effects." Move constructor cannot guarantee no-thrown except if marked noexcept. If the move constructor can throw the compiler has to fall back on copy when reallocating to keep the strong exception guarantee. – Arzar Nov 21 '12 at 18:57
  • @moswald, Jonathan Wakely just did a fantastic explanation about the behavior of gcc 4.7. It is way subtler than I thought. A great read : http://stackoverflow.com/questions/13800858/stdvectorpush-back-a-non-copyable-object-gives-compiler-error#13800858 – Arzar Dec 10 '12 at 23:23
  • @ThomasPetit Thanks! I would have missed that write-up. I wonder if there's a way to subscribe to just the really good questions and answers for tags? – moswald Dec 11 '12 at 00:05
2

This is probably what you want:

struct obj
{
  std::string name;
  int age;
  float money;

  obj()
      : name("NO_NAME")
      , age(0)
      , money(0.0f)
  {
  }

  obj(std::string _name, int _age, float _money)
      : name(std::move(_name))
      , age(std::move(_age))
      , money(std::move(_money))
  {
  }
};

int main(int argc, char* argv[])
{
  std::vector<obj> v;
  for( int i = 0; i < 5000000; ++i )
  {
    v.emplace_back("Jon", 45, 500.6f);
  }
  return(0);
}

Note that I changed your obj(std::string _name, int _age, float _money) constructor to move _name instead of making an unnecessary copy of it.

Your are also calling emplace_back incorrectly, should be emplace_back("Jon", 45, 500.6f).

All the other stuff is automatically optimally generated by the compiler.

ronag
  • 49,529
  • 25
  • 126
  • 221
  • this is comparable to the answer from moswald, the problem is that this is slower if compared to a push_back ... it's not supposed to be faster ? – user1797612 Nov 20 '12 at 13:17
  • It is faster, not sure how you are benchmarking it, but I would guess you are doing something wrong. Maybe you could create a full sample (including timing) on ideone.com? – ronag Nov 20 '12 at 13:20
  • Also, in addition to mosvalds answer I also changed your `obj(name, age, float money)` constructor to move _name instead of making an unnecessary copy of it. – ronag Nov 20 '12 at 13:21
2

The running time is dominated by the construction of the std::string from the string literal, so the difference between a move construction and an emplace construction is trivial.

This takes 400 ms on my machine:

#include <iostream>
#include <vector>

using namespace std;

struct obj
{
    string name;
    int age;
    float money;
};

int main(int argc, char* argv[])
{
    vector<obj> v;
    for( int i = 0; i < 5000000; ++i )
    {
        v.emplace_back(obj{"Jon", 45, 500.6f});
    }
    return v.size();
}

This takes 80 ms on my machine:

#include <iostream>
#include <vector>

using namespace std;

struct obj
{
    int age;
    float money;
};

int main(int argc, char* argv[])
{
    vector<obj> v;
    for( int i = 0; i < 5000000; ++i )
    {
        v.emplace_back(obj{45, 500.6f});
    }
    return v.size();
}

Note that a plain struct will have a sensible default move constructor generated for it.

This already takes 220 ms on my machine:

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char* argv[])
{
    int t = 0;
    for( int i = 0; i < 5000000; ++i )
    {
        string s("Jon");
        t += s.size();
    }
    return t;
}
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • solving this question teaches me a lot, but strings are still treated more like a const pointer than a real string and i don't get why, well i get the part for the C compatibility, but it's really an old approach. thanks for this. – user1797612 Nov 20 '12 at 14:08
  • It is possible to design an immutable string class that will not take a copy of a string literal, however this adds complexity (see Java's String vs StringBuilder classes). It's a design tradeoff. – Andrew Tomazos Nov 20 '12 at 14:27