Quantcast
Channel: CodeSection,代码区,Linux操作系统:Ubuntu_Centos_Debian - CodeSec
Viewing all articles
Browse latest Browse all 11063

Copy versus Move Semantic: A few Numbers

$
0
0

A lot was written about the advantages of the move semantic to the copy semantic. Instead of an expensive copy operation you can use a cheap move operation. But, what does that mean? I will compare in this post the performance of the copy and move semantic for the containers the Standard Template Library (STL).

Before I show the number, I will provide a little bit of background information.

Copy versus move semantic

The subtle difference is, if you create with copy or move semantic a new object based on an existing one, that the copy semantic will copy the elements of the resource, that the move semantic will move the elements of the resource. Of course, copying is expensive, moving is cheap. But there are additional serious consequences.

With copy semantic it can happen, that a std::bad_alloc will be thrown because your program is out of memory. The resource of the move operation is afterwards in a " valid but unspecified state ".

The second point is very nice to show with std::string .

At first, the classical copy semantic.

Copy semantic std::string("ABCDEF");
std::string str2;
str2= str1;
Copy versus Move Semantic: A few Numbers

Both strings str1 and str2 have after the copy operation the same content " ABCDEF ". So, what's the difference to the move semantic.

Move semantic std::string("ABCDEF");
std::string str3;
str2= std::move(str1);
Copy versus Move Semantic: A few Numbers

The string str1 is in opposite to the copy semantic afterwards empty "". This is not guaranteed but often the case. I explicitly requested the move semantic with the function std::move . The compiler will automatically perform the move semantic if it is sure that the source of the move semantic is not needed any more.

I will explicitly request the move semantic in my program by using std::move.

The performance differences

I will take the naive position in my post and compare, what is the performance difference between the copy and move semantic of the STL containers. My comparison will include the std::string . I will ignore the associative containers , which can have more equal keys. I'm in particular interested in the performance ratio between the copy and move semantic of the containers.

The boundary conditions

The differences were not so dramatically between the program with maximum optimization and without optimization therefore I will for simplicity reasons only provide the results for the executable with maximum optimization. I use an GCC 4.9.2 compiler and the cl.exe compiler, which is part of Microsoft Visual Studio 2015. Both platforms are 64-bit. Therefore, the executables are build for 64-bit.

The program

We have a lot of containers in the STL. Therefore, the program is a little bit lengthy.

// movePerformance.cpp
#include <array>
#include <forward_list>
#include <chrono>
#include <deque>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
const int SIZE = 10000000;
template <typename T>
void measurePerformance(T& t, const std::string& cont){
std::cout << std::fixed << std::setprecision(10);
auto begin= std::chrono::system_clock::now();
T t1(t);
auto last= std::chrono::system_clock::now() - begin;
std::cout << cont << std::endl;
auto copyTime= std::chrono::duration<double>(last).count();
std::cout << " Copy: " << copyTime << " sec" << std::endl;
begin= std::chrono::system_clock::now();
T t2(std::move(t));
last= std::chrono::system_clock::now() - begin;
auto moveTime= std::chrono::duration<double>(last).count();
std::cout << " Move: " << moveTime << " sec" << std::endl;
std::cout << std::setprecision(2);
std::cout << " Ratio (copy time/move time): " << (copyTime/moveTime) << std::endl;
std::cout << std::endl;
}
int main(){
std::cout << std::endl;
{
std::array<int,SIZE/1000> myArray;
measurePerformance(myArray,"std::array<int,SIZE/1000>");
}
{
std::vector<int> myVec(SIZE);
measurePerformance(myVec,"std::vector<int>(SIZE)");
}
{
std::deque<int>myDec(SIZE);
measurePerformance(myDec,"std::deque<int>(SIZE)");
}
{
std::list<int>myList(SIZE);
measurePerformance(myList,"std::list<int>(SIZE)");
}
{
std::forward_list<int>myForwardList(SIZE);
measurePerformance(myForwardList,"std::forward_list<int>(SIZE)");
}
{
std::string myString(SIZE,' ');
measurePerformance(myString,"std::string(SIZE,' ')");
}
std::vector<int> tmpVec(SIZE);
std::iota(tmpVec.begin(),tmpVec.end(),0);
{
std::set<int>mySet(tmpVec.begin(),tmpVec.end());
measurePerformance(mySet,"std::set<int>");
}
{
std::unordered_set<int>myUnorderedSet(tmpVec.begin(),tmpVec.end());
measurePerformance(myUnorderedSet,"std::unordered_set<int>");
}
{
std::map<int,int>myMap;
for (auto i= 0; i <= SIZE; ++i) myMap[i]= i;
measurePerformance(myMap,"std::map<int,int>");
}
{
std::unordered_map<int,int>myUnorderedMap;
for (auto i= 0; i <= SIZE; ++i) myUnorderedMap[i]= i;
measurePerformance(myUnorderedMap,"std::unordered_map<int,int>");
}
}

The idea of the program is it to initializethe containers with 10 million elements. Of course, the initialization will happen with copy and move semantic. The performance measurement takes place in the function template measurePerformane (line 21 - 44). The function takes as argument the container and the name of the container. Thanks to the chrono library I can measure how long the copy initialization (line 27) and the move initialization (line 34) takes. At the end I'm interested in the ratio between the copy and move semantic (line 40).

What's happening in the main function? I create for each container an own scope so that it will be automatically released. Therefore, myArray (line 51) will automatically be released and the end of its scope (line 53). Because the containers are quite big, releasing their memory is a must. I claimed that each container has 10 millions elements. That will not hold for myArray. Because myArray will not be allocated on the heap, I have to dramatically reduce its size. But now to the remaining containers. With std::vector, std::deque, std::list , and std::forward_list there are in line 55 - 73 the remaining sequential containers. In line 75 - 78 std::string follows. The rest are the associative containers. I have to pay attention to one characteristic of the associative container. In order to have unique keys and therefore the size 10 milli

Viewing all articles
Browse latest Browse all 11063

Trending Articles