9

A lot of the focus on performance in C++ is to reduce useless copying.

The language itself comes with a lot of nice features that help in this regard, including move semantics, perfect forwarding, etc.

There are times when is hard to see the useless copies.

Also, some of these copies can be made by casting or assigning to the wrong type at the wrong time, which can end up being expensive, especially when using lazy evaluation libraries.

Besides measuring time performance and looking close at code that seem to be suspiciously slow, is there a better way of avoiding these issues? Maybe some form of memory benchmarking procedure?

Paul92
  • 8,827
  • 1
  • 23
  • 37
  • 2
    Require expensive-to-copy objects to be copied explicitly? – Max Langhof Dec 05 '19 at 13:11
  • @MaxLanghof This is only a partial solution. In practice, many such objects come from libraries over which there is little or no control. – Paul92 Dec 05 '19 at 13:13
  • I don't think it's a good solution either to be honest. The real cost is `numCopyOperations * costPerCopy`, and trying to work it out from only the latter term is not very useful. Coincidentally, this relation is also why you will most likely need some form of profiling to find the real culprits. There are all kinds of profilers out there (not just what you seem to consider "performance" ones), did you go look? :) – Max Langhof Dec 05 '19 at 13:16
  • Can you modify the corresponding classes? If so, I can imagine to add some conditionally-compiled profiling code that would check for cases, where an object has been created by copy constructor and then destroyed, while no other _usefull_ member function has been called in between. Of course, it would be better to instrument the class automatically with some tool, but I am not aware of any such one. – Daniel Langr Dec 05 '19 at 13:19
  • @MaxLanghof I know there are tools out there, but was looking for some practical advice on avoiding this issue. Workflows, personal experience, rules of thumb people follow in practice. – Paul92 Dec 05 '19 at 13:23
  • @DanielsaysreinstateMonica The general case is that the classes are closed. Of course, one could even write instrumented wrappers for classes, but this seems to be a bit of mostly overkill. – Paul92 Dec 05 '19 at 13:25
  • 1
    @Paul92 And, the build process of libraries is also closed? If so, I am afraid there would be no possibility to track copies inside libraries, where copy constructors and destructors may be inlined. How to recognize which instructions were generated from copy constructor source code? – Daniel Langr Dec 05 '19 at 13:27
  • @Paul92 Note that asking for "workflows, personal experience, rules of thumb" is at least borderline off-topic. But I don't think the current phrasing of the question is objectionable. – Max Langhof Dec 05 '19 at 13:41

1 Answers1

-1

In practice, many such objects come from libraries over which there is little or no control.

Sometimes it can be good to wrap the libraries if the library api can be subject to change and the amount of API used is small. In this case, you can disable copying of expensive objects and require them to be copied, say, via a "clone" method. This can be done by making the copy-ctor private and adding a public clone method that just returns *this.

Otherwise, use references/pointers whenever possible(remember: const T& can hold a temporary) to avoid useless copying in your code, and if the library does unneccesary copies, well, you can't really avoid them. If you want to pass an object to a library and let go of it, considering std::move-ing. That way, the object WILL be moved, regardless if the library has explicit support for moving.

00001H
  • 22
  • 1
  • 7
  • *"This can be done by making the copy-ctor private"* or better yet, [explicitly disable it](https://stackoverflow.com/q/13654927/16835308) using `= delete;`. – Zakk Jan 30 '23 at 10:12
  • @Zakk that way, the "clone" method can still use it to copy it, if absolutely needed. – 00001H Jan 30 '23 at 14:12
  • last part is not correct / misleading. `std::move` is not actually moving, its just a cast, and that cast has zero effect if the libary does not support moving. – 463035818_is_not_an_ai Feb 01 '23 at 06:04
  • @463035818_is_not_a_number It is just a cast, however, if the parameter type supports moving, even if the accepting function does nothing, it will be moved. – 00001H Feb 01 '23 at 14:14
  • If the parameter type does NOT support moving(move ctor is private/deleted), the call will be an error, and still no copies are made. – 00001H Feb 01 '23 at 14:17
  • Therefore, `std::move` forces a move. – 00001H Feb 01 '23 at 14:18
  • Unless you can convince me otherwise, `std::move` does in fact make copying impossible. And does NOT have zero effect under any circumstances. @463035818_is_not_a_number – 00001H Feb 01 '23 at 14:18
  • Here is an example of `std::move` having no effect. It does not make copying impossible. https://godbolt.org/z/jadTPa48Y – 463035818_is_not_an_ai Feb 01 '23 at 15:20
  • @463035818_is_not_a_number that's because the move ctor is nonexistent. https://godbolt.org/z/TGbf5h5c3 – 00001H Feb 02 '23 at 06:08
  • @463035818_is_not_a_number And if the library implements a copy ctor and NOT the move ctor, [it is trash](https://archive.org/details/cpp-primer-5th-edition/page/505/mode/2up). (see p. 506) `All five copycontrol members should be thought of as a unit: Ordinarily, if a class defines any of these operations, it usually should define them all.` – 00001H Feb 02 '23 at 06:16