目录
摘要
代码性能的优化,包括连续内存的访问和stl中move等使用的介绍。
一、访问内存的优化
1. 函数内存的引用
add_poor对形参dst的每次修改, add_better计算end后再赋值。
//array的求和
void add_poor(const int *src, int n, int *dest)
{
for (int i = 0; i < n; ++i)
{
*dest += src[i];
}
}
void add_better(const int *src, int n, int *dest)
{
int sum = *dest;
for (int i = 0; i < n; ++i)
{
sum += src[i];
}
*dest = sum;
}
//the delta time
auto start_time = std::chrono::system_clock::now();
//do something
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end_time -start_time;
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
std::cout<<"deata ms is: "<< duration.count()<<std::endl;
2. 顺序的访问
多维数组是以行主序存储在内存中的,那么在循环中访问完一行后再访问下一行的方式更高效, 尽量避免不连续的访问。
// 按列访问数组元素
long get_poor(const int *src, int rows, int cols)
{
long long sum = 0;
for (int i = 0; i < cols; ++i)
{
for (int j = 0; j < rows; ++j)
{
sum += *(src + j * cols + i);
}
}
return sum;
}
// 按行访问数组元素
long get_better(const int *src, int rows, int cols)
{
long long sum = 0;
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
sum += *(src + i * cols + j);
}
}
return sum;
}
二、stl库的使用
1. reserver
预分配n个元素的存储空间,std::vector添加的元素已知,那么在添加元素前使用reserve函数预分配std::vector需要的内存,避免内部数据发生数据的迁移带来不必要的元素拷贝开销、申请新内存、释放内存,第一个元素的地址&data[0]都一直不变。即在这添加这些元素时,std::vector内部未发生数据迁移,在添加后面的其他元素时,也不会导致std::vector内部数据迁移。
void testReserve()
{
std::vector<int> myVec;
//myVec.reserve(100); // 新元素还没有构造,此时不能用[]访问元素
for (int i = 0; i < 100; i++ )
{
myVec.push_back(i); //新元素这时才构造
}
myVec.resize(102); // 用元素的默认构造函数构造了两个新的元素
myVec[100] = 1; //直接操作新元素
myVec[101] = 2;
}
vector<int> myVec, 然后500次调用 myVec.push_back(****) ; vector<int> myVec(500), 然后500次调用 myVec.push_back(****); 做法2只需要进行1到2次内存分配,而做法1不知道要进行多少次内存分配了。
2. emplace_back
push_back: ori200 copy 200 move 0 dest 200; emplce_back: ori200 copy 0 move 0 dest 0
push_back函数的三种开销:临时对象的构造和拷贝开销 析构开销
emplace_back函数只有必要的构造元素开销,不会引入临时对象. 此时只调用了一次构造函数,连移动构造函数都省掉了,这是因为emplace_back把参数10完美转发给A的构造函数,直接构造了一个元素,而这个元素是直接存放在vector容器中的
int g_count_ori = 0;
int g_count_copy = 0;
int g_count_move = 0;
int g_count_des = 0;
class TestObject
{
public:
explicit TestObject(int a, int b, int c): a_(a), b_(b), c_(c)
{
g_count_ori++;
}
TestObject(const TestObject & other)
{
g_count_copy++;
}
TestObject(const TestObject && other)
{
g_count_move++;
}
~TestObject()
{
g_count_des ++;
}
int a_, b_, c_;
};
void poor(std::vector<TestObject> &data)
{
data.reserve(N);
for (int i = 0; i < N; i++)
{
data.push_back(TestObject(i, i+1, i+2));
}
}
void better(std::vector<TestObject> &data)
{
data.reserve(N);
for (int i = 0; i < N; i++)
{
//因为emplace_back把参数10完美转发给A的构造函数,直接构造了一个元素
//data.emplace_back((i, i+1, i+2)); //error
//如果一个类没有拷贝或移动构造,则不能用于STL容器中,如果没有相应参数类型的构造实现,
//emplace_back编译不过,找不到它需要的对应构造函数
data.emplace_back(i, i+1, i+2);
//data.emplace_back(std::move(TestObject(i, i+1, i+2)));
}
}
3. move
移动而非拷贝数据以避免不必要的拷贝开销,从而改善程序性能
class MemoryBlock
{
public:
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length)
: _length(length), _data(new int[length])
{
std::cout << "ori construct. length = "<< _length << std::endl;
}
// Destructor.
~MemoryBlock()
{
std::cout << "deconstruc. length = "<< _length;
if (_data != nullptr) {
delete[] _data;
}
}
// Copy constructor.
MemoryBlock(const MemoryBlock& other)
: _length(other._length), _data(new int[other._length])
{
std::cout << "copy constructor length = "<< other._length << std::endl;
std::copy(other._data, other._data + _length, _data);
}
// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
std::cout << "Copy assignment operator length = "
<< other._length << std::endl;
if (this != &other) {
delete[] _data;
_length = other._length;
_data = new int[_length];
std::copy(other._data, other._data + _length, _data);
}
return *this;
}
size_t Length() const
{
return _length;
}
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _data(nullptr), _length(0)
{
std::cout << "Move constructor. length = "<< other._length << std::endl;
// Copy the data pointer and its length from the source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object
other._data = nullptr;
other._length = 0;
}
// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other)
{
std::cout << "move operator length = "
<< other._length << "." << std::endl;
if (this != &other) {
delete[] _data;
// Copy the data pointer and its length from the source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
}
return *this;
}
private:
size_t _length; // The length of the resource.
int* _data; // The resource.
};
void TestSTLObject()
{
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload
v.push_back(str);
std::cout << "After copy, str is " << str << std::endl;
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str));
std::cout << "After move, str is " << str <<std::endl;
/*
After copy, str is "Hello"
After move, str is ""
*/
}
void TestMyObjectWithoutUseMove()
{
std::vector<MemoryBlock> v;
MemoryBlock mb1(25);
v.push_back(mb1);
/*
ori construct. length = 25
copy constructor length = 25
deconstruc. length = 25 Deleting resource.
deconstruc. length = 25 Deleting resource.
*/
}
void TestMyObjectWithUseMove()
{
std::vector<MemoryBlock> v;
MemoryBlock mb1(25);
v.push_back(std::move(mb1));
/*
ori construct. length = 25
Move constructor. length = 25
deconstruc. length = 0
deconstruc. length = 25
*/
}
A: 左值的声明符号为&,右值的声明符号为&&。
B: 深层拷贝对程序的影响比较大. 把临时的对象直接移动给被赋值的左值对象,效率是显著的, 就产生了移动语义,右值引用是用来支持转移语义的。
C: 移动语义可以将资源(堆,系统对象等) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁. 移动语义通过移动构造函数和移动赋值操作符实现,其与拷贝构造函数类似,区别如下:
参数的符号必须为右值引用符号,即为&&; 参数的成员转移后需要修改(如改为nullptr),避免临时对象的析构函数将资源释放掉
总结
内存的连续的访问和std::emplace_back