Performance: Deep vs Shallow Copying
What are the performance implications of deep copying versus shallow copying?
The choice between deep copying and shallow copying can have significant performance implications in C++. Understanding these implications is crucial for writing efficient code, especially when dealing with large objects or frequently copied data structures.
Shallow Copying
Shallow copying involves copying only the immediate data members of an object. For primitive types and pointers, this means copying the values directly. For pointers, the address is copied, not the pointed-to data.
Performance characteristics:
- Fast: Only copies the direct members of the object.
- Low memory usage: Doesn't duplicate underlying data for pointer members.
- Constant time complexity: regardless of object size.
Example of shallow copying:
#include <chrono>
#include <iostream>
class ShallowPlayer {
public:
ShallowPlayer(int* hp) : health(hp) {}
int* health;
};
int main() {
const int numCopies = 1'000'000;
int hp = 100;
auto start =
std::chrono::high_resolution_clock::now();
for (int i = 0; i < numCopies; ++i) {
ShallowPlayer original(&hp);
// Shallow copy
ShallowPlayer copy = original;
}
auto end =
std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end -
start;
std::cout << "Time to perform " << numCopies
<< " shallow copies: " << diff.count() << "s";
}
Time to perform 1000000 shallow copies: 0.0021545s
Deep Copying
Deep copying involves creating a new copy of all data owned by the object, including dynamically allocated memory that the object might be pointing to.
Performance characteristics:
- Slower: Needs to allocate new memory and copy all underlying data.
- Higher memory usage: Duplicates all data, leading to increased memory consumption.
- Linear time complexity: where n is the size of the underlying data.
Example of deep copying:
#include <chrono>
#include <iostream>
#include <memory>
class DeepPlayer {
public:
DeepPlayer(int hp) : health(
std::make_unique<int>(hp)) {}
DeepPlayer(const DeepPlayer& other)
: health(
std::make_unique<int>(*other.health)) {}
std::unique_ptr<int> health;
};
int main() {
const int numCopies = 1'000'000;
auto start =
std::chrono::high_resolution_clock::now();
for (int i = 0; i < numCopies; ++i) {
DeepPlayer original(100);
DeepPlayer copy = original; // Deep copy
}
auto end =
std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end -
start;
std::cout << "Time to perform " << numCopies
<< " deep copies: " << diff.count() << "s";
}
Time to perform 1000000 deep copies: 0.449998s
Running both these programs will show a significant performance difference, with shallow copying being much faster.
Considerations
- Shallow copying is faster but can lead to issues with shared mutable state.
- Deep copying is safer for managing independent objects but comes with a performance cost.
- The choice depends on your specific use case. Sometimes, a mix of both approaches (like copy-on-write) can be optimal.
- For large objects or collections, the performance difference can be substantial and should be carefully considered in performance-critical code.
Remember, the right choice depends on your specific requirements for correctness, memory usage, and performance. Always profile your code to make informed decisions about copying strategies.
Copy Constructors and Operators
Explore advanced techniques for managing object copying and resource allocation