5 ms·
> std::vector uses constructors and destructors to create and destroy objects which in some cases can be significantly slower than memcpy(). This is precisely
by slimscsi 7y ago
> std::vector uses constructors and destructors to create and destroy objects which in some cases can be significantly slower than memcpy().
This is precisely what vector::emplace() solves, and std::move should be faster than swap and pop.
Modern C++ has changed a lot, this article ignores the massive improvements added in c++11,14,17.
- codesushi42 7y agoIt is better to use push_back over emplace to be explicit about which constructor will be called.
- Koshkin 7y agoBut emplace() is already as explicit about it as it gets.
- codesushi42 7y agoNuh uh. If you're not careful, it will call an implicit constructor.
- wrsh07 7y ago+1, Google suggests doing this as well: https://abseil.io/tips/112 https://abseil.io/tips/112 > So in general, if both push_back() and emplace_back() would work with the same arguments, you should prefer push_back(), and likewise for insert() vs. emplace().
- daemin 7y agoThat's an interesting point the tip makes. Is there guidance on how to use the emplace_back() added to c++17 which returns a reference to the constructed element? The reference returning emplace_back() is used frequently in the code to construct a new element of a struct and then fill in its members, as opposed to creating a new struct then push_back() to copy the memory in.
- flohofwoe 7y agovector::emplace() still needs to construct the object, it just happens inplace and avoids a redundant copy of an already constructed object. Same with std::move(). As such the blog post is correct. Using POD structs which can be zero-initialized and memcpy'ed may indeed be faster, especially when these are bulk-operations.
- Asooka 7y agoNo, the problem is that std::vector still calls the constructor and destructor of each and every object in the array at least once. This is a performance loss if they don't do anything - you have to rely on the compiler to inline the call, then remove the code. For POD datastructures it can be significant, because those are usually the largest arrays in your application. This is why e.g. Facebook's Folly library detects POD types in their vector and doesn't call ctors and dtors at all. Similarly, std::vector has to allocate more memory every time it has to grow and copy all its contents, whereas for POD datatypes you can just use realloc which can save copies. These are all borderline microoptimisations, but they matter for realtime highly responsive software. Or just in general when you need to squeeze out every last bit of performance.
- dymk 7y agostd::move'ing a vector does not call the ctor/dtor of every element within the vector, but that might not be what you're referring to. If you want an `A` struct/class, you'll call the ctor/dtor, that's true. But for POD types, if the ctor/dtor does nothing, they are trivial to inline and will incur no runtime overhead by any compiler nowadays.
- arximboldi 7y agoAFAIK std::vector does use memcpy (std::copy as well) when objects are trivially copyable.
- AshamedCaptain 7y agoThat is correct yet the compilers definition of what is trivially copyable might be more strict than what you expect. For example, objects that are trivially relocatable can also be memcpy'd for reserve/realloc, but the compiler will not be able to figure that on its own. std::vector itself falls in this category: trivially relocatable, definitely not trivially copyable. So a vector of vectors will not necessarily be able to use memcpy but rather fall back to copy/move assignment. This is not very significant in performance for this type (vector move being cheap) but a language gotcha nonetheless (as the move constructor will be called n times in every capacity change)
- gpderetta 7y agoTrivially copyable is a word of power (well, two words I guess), it's meaning is well defined and you can statically assert for it. What unfortunately is not defined is (trivially) relocatable as that's not a property that can be safely be inferred so it is not (yet) part of the standard. Some libraries still have this concept and require some sort of opt in.
- hermitdev 7y agoSince C++11, you can use template traits to determine if a type is trivially copyable, and even add static_asserts to your code to ensure future changes dont break expectations.
- AshamedCaptain 7y agoThe article is just doing a generic cargo cult warning there. Not bad as a general C++ gotcha warning, but definitely incorrect in this specific case. As per the author's constraints these are "POD types that are trivially memcpy-copyable", so by definition the copy constructors will never do anything. Much less "allocate memory" as the author claims.
- stephc_int13 7y agoDid you really read the article in full? Are you sure you understood it?
- adrianN 7y agoFrom the Guidelines: > Please don't comment on whether someone read an article. "Did you even read the article? It mentions that" can be shortened to "The article mentions that."
- CodeMage 7y ago> This is precisely what vector::emplace() solves, and std::move should be faster than swap and pop. The whole swap-and-pop section weirded me out. Maybe I just don't know enough about C++, but saying that assignment (a[i] = a[n-1]) will call the destructor seems false. As far as I know, the compiler should generate an implicitly defined copy assignment operator for these fixed size PODs and it should be as performant as memcpy. But again, I don't have years and years of in-depth C++ experience, so I would be grateful if an expert could shed more light on this.
- foota 7y agoYeah, fairly certain that is wrong. I think that would just call the copy assignment operator, would it not? For correctness you would probably then follow up with a pop_back to keep the vector right-sized. Actually you'd probably want to do: a[i] = std::move(a[n - 1]); Then follow up with pop_back. Best would probably be: a[i] = std::move(a.erase(n-1));
- hermitdev 7y agoIdeally, the std lib implementation should handle that detail for you...
- gpderetta 7y agowhich detail?
- foota 7y agoIn theory erase could return a move iterator, meaning that you could omit the call to std::move. This wouldn't be backwards compatible though so not going to happen.
- gpderetta 7y agowait, how is this supposed to work? a[i] = std::move(a.erase(n-1)); There is no erase that takes an index, so I assume that n = a.end(). Also it is missing a dereference: a[i] = std::move(*a.erase(a.end()-1)); but erasing the one-before-the-end returns the (new) end iterator, which obviously is not referenceable. In general, after calling erase, it is too late to access the erased element. You want something like: template<class Container, class Iter> auto erase_and_return(Container&& c, Iter pos) { auto x = std::move(*pos); c.erase(pos); return x; } Also in the general case it doesn't make sense for erase to return a move iterator.
- kcbanner 7y agoIn most C++ game engines the standard library is almost never used, for performance reasons. See: https://github.com/electronicarts/EASTL https://github.com/electronicarts/EASTL
- favorited 7y agoPerformance in debug builds is a particular issue, since getting acceptable-for-gamedev machine code from modern C++ often requires optimized builds. http://aras-p.info/blog/2018/12/28/Modern-C-Lamentations/ http://aras-p.info/blog/2018/12/28/Modern-C-Lamentations/
- hermitdev 7y agoFor MSVC, the debug checks are fairly customizable through judicious use of appropriate debug macros. One can also enabled optimizations with debug symbols, but the debugging experience can be jarring. I'm not a game developer, but have spent a decade doing C++ on Windows, and at former employer, we had several different debugging profiles depending on the severity/difficulty of reproducing/debugging an issue. Our "normal" debug profile had all of the debug checks in the std lib disabled, and we could only effectively debug our own code. Not sure if games dont do this, or if its still not performing enough.
- daemin 7y agoOne problem with using different debug macros in your debug build is that any libraries you link in must also be using the same flags. This is not necessarily possible for binary releases as they will assume certain standard library flags to exist in the debug builds (like iterator checking levels). At work we don't use a debug build in the traditional sense, it's what you call a no-optimisations build where the code is compiled without most optimisations but otherwise the flags are the same as a release build. Some teams also go a step further and compile most of the code in release but some of their code with optimisations disabled.
- hermitdev 7y ago
- NCG_Mike 7y agoIt seems clear from the article the developer isn't that clued up.
- DerDangDerDang 7y agoIt's not clear from the article, but I suspect the author is talking about what happens when the vector is resized and has to move existing elements, which is a real problem. There are plans to solve this - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1144r0.html http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p114...