C++ | 高效使用vector
文章目录
- C++ | 高效使用vector
- 1.善用Reserve
- 2.移动构造和赋值
- 3.释放vector
- 如果vector内存的是指针,需要先释放每个指针所指内存,再释放vector
- Reference
1.善用Reserve
当需要向vector中添加元素但目前的空间已经放满时,vector会再分配一块更大的空间,先将已有元素拷贝或移动过去,再添加新的元素。
如果频繁向vector中存入元素,就会造成频繁的内存分配和元素移动。
因此如果预知存入vector成员的个数,可以使用reserve分配好内存空间,避免空间不够的时候再次分配空间。
#include <string>
#include <vector>
#include <iostream>
#include <chrono>
using namespace std::chrono;
void randy_reserve() {
for(int i = 0; i < 1024 * 1024; i++) {
std::vector<int> randy;
randy.reserve(10000);
for(int j = 0; j < 10000; j++) {
randy.push_back(j);
}
}
}
void randy_without_reserve() {
for(int i = 0; i < 1024 * 1024; i++) {
std::vector<int> randy;
for(int j = 0; j < 10000; j++) {
randy.push_back(j);
}
}
}
template<typename F>
void timeit(std::string sanjiejiyuan, F &&f) {
auto st = steady_clock::now();
f();
auto ed = steady_clock::now();
auto diff = duration_cast<duration<double>>(ed - st);
std::cout << sanjiejiyuan << diff.count() << " seconds" << std::endl;
}
int main() {
timeit("use reserve: ", randy_reserve);
timeit("no use reserve: ", randy_without_reserve);
return 0;
}
结果:
vector 大小为100时耗时
use reserve: 1.04476 seconds
no use reserve: 1.8501 seconds
vector 大小为10000时耗时
use reserve: 95.5728 seconds
no use reserve: 93.7059 seconds
可以看出来,当vector较小时,频繁使用效果比较明显。所以当能够明确知道需要存储多少数据量到vector中去的时候,就提前预留多少内存空间,可以避免vector因为空间不够而不断开辟空间,造成时间消耗。
2.移动构造和赋值
如果将拷贝自定义类型到vector的方式改成移动构造和赋值,能够顾减少不必要的性能开销。
vector移动元素的场景:
- 扩展内存空间,拷贝元素到新的内存空间
- 插入非尾部位置的元素时,移动该元素后面元素到尾部元素向后1格
- 删除元素(非尾部)时,移动该元素后面元素到尾部元素向前1格
- 不同vector相互赋值等
class Copy {
public:
Copy() noexcept : randy(1024, 'q') {}
Copy(const Copy &sesame) noexcept : randy(sesame.randy) {}
Copy(Copy &&) = delete;
~Copy() {}
private:
std::string randy;
};
class FakeMove {
public:
FakeMove() noexcept : randy(1024, 'q') {}
FakeMove(FakeMove &&sesame) : randy(std::move(sesame.randy)) {}
FakeMove(const FakeMove &sesame) noexcept : randy(sesame.randy) {}
~FakeMove() {}
private:
std::string randy;
};
class Move {
public:
Move() noexcept : randy(1024, 'q') {}
Move(Move &&sesame) noexcept : randy(std::move(sesame.randy)) {}
Move(const Move &sesame) noexcept : randy(std::move(sesame.randy)) {}
~Move() {}
private:
std::string randy;
};
void QCopy() {
for(int i = 0; i < 4096; i++) {
std::vector<Copy> target;
for(int j = 0; j < 150; j++) {
Copy c;
target.push_back(c);
}
}
}
template<typename T>
void QMove() {
for(int i = 0; i < 4096; i++) {
std::vector<T> target;
for(int j = 0; j < 150; j++) {
T h;
target.emplace_back(h);
}
}
}
int main() {
timeit("Copy: ", QCopy);
timeit("FakeMove: ", QMove<FakeMove>);
timeit("Move: ", QMove<Move>);
return 0;
}
结果:
Copy: 0.24857 seconds
FakeMove: 0.150558 seconds
Move: 0.108038 seconds
Move 类声明移动构造时的noexcept
保证不抛出异常。noexcept
编译期完成声明和检查工作,表示函数不会抛出异常。
3.释放vector
在某个时间点后再也不会用到vector了,可以释放它所占用的空间及其成员所占用的空间。
int main() {
std::vector<int> randy;
for (int i = 0; i < 20; i++) {
randy.push_back(i);
}
std::cout << "randy size: " << randy.size() << " capacity: " << randy.capacity()
<< std::endl;
randy.clear();
std::cout << "after clear, randy size: " << randy.size() << " capacity: " << randy.capacity()
<< std::endl;
std::vector<int>().swap(randy);
std::cout << "swap with empty vector, randy size: " << randy.size() << " capacity: " << randy.capacity()
<< std::endl;
return 0;
}
运行结果:
randy size: 20 capacity: 32
after clear, randy size: 0 capacity: 32
swap with empty vector, randy size: 0 capacity: 0
真正释放内存是在vector的析构函数里进行的,所以一旦超出vector的作用域,首先它所保存的所有对象会被析构,然后会调用allocator中的deallocate函数回收对象本身的内存。
上述释放内存代码 std::vector<int>().swap(randy);
可以替换为以下形式:
{
std::vector<int> randy;
for (int i = 0; i < 20; i++) {
randy.push_back(i);
}
} // 离开作用域,randy会调用自己的析构函数释放内存
如果vector内存的是指针,需要先释放每个指针所指内存,再释放vector
std::vector<Randy *> sanjie(20, new Randy());
for (size_t i = 0; i < 20; i++) {
delete sanjie[i];
sanjie[i] = nullptr;
}
randy.clear();
std::vector<Randy *>().swap(sanjie);
Reference
- std::vector的小技巧
- vector 动态删除元素,释放内存的研究
欢迎关注公众号【三戒纪元】