0

In a member function, I want to return a newly created vector of strings. Which version is the most efficient from a memory allocation & constructor calling point of view? theString(i) returns a const std::string &

std::vector<std::string> result(depth());
for(int i=0;i<depth;i++) {
    result[i] = theString(i);
}

or

std::vector<std::string> result;
result.reserve(depth());
for(int i=0;i<depth;i++) {
    result.emplace_back(theString(i));
}

To me it seems that:

  • Solution 1 constructs every empty string first, then copy-assigns them => not perfect
  • Solution 2 is better since it will copy-construct every string, while the vector data is allocated once by reserve

(or is there an even more efficient solution?)

galinette
  • 8,896
  • 2
  • 36
  • 87
  • which code snippet is easier to maintain, or to ignore when hunting for a bug, so on – Cheers and hth. - Alf May 11 '14 at 17:30
  • 2
    I would imagine that your compiler would optimize both to the same code, check the assembly output for both and see if there are any differences. As Alf mentioned, readability is far more important than these micro optimizations though. So go with whatever is easiest to read and maintain. – shuttle87 May 11 '14 at 17:30
  • 1
    Well, there is no much difference in readability, and it's definitely in a speed critical section. – galinette May 11 '14 at 17:32
  • 2
    [The Rules of Optimization Club](http://www.perlmonks.org/?node_id=962631) Apply the second rule. – jsantander May 11 '14 at 17:38
  • @shuttle87: actually, that's pretty unlikely. But whether it makes a difference in speed is another question. – Matthieu M. May 11 '14 at 17:40
  • @MatthieuM, I just looked up `emplace_back` and it is a bit different to what I thought. So yes it might be different code produced. Given that it's in a speed critical section I would imagine that profiling the code is the best way to go. – shuttle87 May 11 '14 at 17:44
  • The best thing would be to have theString() take a vector as additional argument and construct the string via emplace_back directly in the vector. That would avoid the copy that needs to happen in any case, unless you have the vector hold references or pointers. It's not a far jump to make the collection a template argument to support lists etc. as well. – Peter - Reinstate Monica May 11 '14 at 17:50
  • @PeterSchneider: actually, I would be surprised if it improved things. out-parameters were never really useful in C++ to start with, and they are even less now that we have move semantics. – Matthieu M. May 11 '14 at 17:52
  • @MatthieuM.: Will the string whose reference is returned by theString(i) indeed be moved if theString(i) returns just a const &? Btw, I meant a vector **ref** or pointer as additional argument, of course. – Peter - Reinstate Monica May 11 '14 at 17:57
  • @PeterSchneider: no, a copy must occur. Which is no different than if `std::vector&` was passed to `theString` I suppose, because it must return a `const&` to something that already exists anything. – Matthieu M. May 11 '14 at 18:11
  • Is there any chance you could tell us how that `std::vector` is used ? Because of course the most efficient way would be not to create the `vector` in the first place. – Matthieu M. May 11 '14 at 18:12
  • @MatthieuM. Thought it could be a factory. – Peter - Reinstate Monica May 11 '14 at 18:55

1 Answers1

2

It's impossible to give a generic answer: depending on your compiler, the Standard Library implementation than you are using, the target CPU and the surrounding source code, the emitted code may vary. Furthermore, whether one solution or the other is faster may also be affected by cache effects or thread-switching effects caused by different scheduling.

So, unfortunately, there is no generic answer: measure with your setup.

That being said, let me offer two other candidates; candidate 3:

// The simplest code is always easier to read:
std::vector<std::string> result;
for (size_t i = 0; i < depth; ++i) { result.emplace_back(theString(i)); }

I would be surprised if it were much worse than your candidate 2. It relies on vector's amortized constant emplace_back and the fact that moving strings is cheap (compared to copying).

And candidate 4:

// If no copy is necessary, then no copy is cheaper:
std::vector<std::string_view> result; // or another "StringRef" alternative
for (size_t i = 0; i < depth; ++i) { result.emplace_back(theString(i)); }

The latter relying on string_view: a non-owning reference to a string which is a very simply thing to implement (basically, a pair size_t and char const*) but is only viable if the referenced string is guaranteed to outlive the last use of the string_view.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722