最近做一定规模的数据读取,二维vector,行约300k,每列大约10~100个int。一直在纠结以下问题:
- 方法一:在循环的外边预先声明一个vector(预申请足够大空间),然后在循环内部做
resize
操作,然后对应位置直接赋值 - 方法二:在循环的外边预先声明一个vector(预申请足够大空间),然后在循环内部做
clear
操作,然后再用push_back
插入 - 方法三:每次在循环中声明一个vector局部变量(预申请足够大空间),用
push_back
插入
显然
内存拷贝
是vector最耗时的操作,所以上述都在声明时直接指定需要的空间。如果不知道需要的空间,则取可能情况中足够大的即可。
上述三种方法是否有明显的时间差异?
直观上来说,第一种、第二种相较于第三种应该是更加快一些的,因为没有局部vector的构造与销毁。但是,方法一与方法三哪个更快呢?
当然要写代码做实验。
不过,在写之前,加入一组猜想地、可能最慢的对照实验:在方法三的基础上,每次声明vector时定义一个默认vector,即不预申请空间。定义其为方法0 ;
实验设置
平台及编译器:RedHat , GCC 4.8.5
迭代轮次: 0x100000 (1M) 次
实验结果
方法 | 时间(s) |
---|---|
Method 0 (In + No-Alloc + push_back ) | 13.2011 |
Method 1 (Out + Pre-Alloc + Resize + assign) | 1.61622 |
Method 2 (Out + Pre-Alloc + Clear + push_back ) | 10.9568 |
Method 3 (In + Pre-Alloc + assign) | 2.43724 |
结果总结
实验结果不得不说有点让人吃惊啊….
- 使用外部定义且预申请空间的方法2竟然耗时与内部定义+不预申请空间的方法0在一个量级… 而他们的共同点是,用了
push_back
- 即使每次在循环内部声明局部vector且预申请空间,但最后时间差距与方法1差别不大。
WHY
难道push_back
会导致内存的重新分配? 官方文档说:
This effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if -and only if- the new vector size surpasses the current vector capacity.
对于clear
官方文档中说clear
A reallocation is not guaranteed to happen
看来还需要把capacity打印一下… 有空做一下吧…
结论
- 在循环外部定义与在循环内部定义vetor并不会带来效率的显著变化(1百万次迭代导致不到0.8s的差距)
- 预申请空间,使用赋值而非
push_back
性能很好 - 外部定义+预申请空间+resize+赋值,效果最佳
附录
测试代码如下:
#include <iostream>
#include <vector>
#include <array>
#include <chrono>
#include <random>
using namespace std ;
int main(int argc , char *argv[] )
{
unsigned seed = chrono::high_resolution_clock::now().time_since_epoch().count() ;
mt19937 rand_generator(seed) ;
uniform_int_distribution<int> dist(6,666) ;
const int MaxIteNum = 0x100000 ;
array<int , MaxIteNum> vec_size ;
chrono::high_resolution_clock::time_point start_tp = chrono::high_resolution_clock::now() ;
for(size_t cnt = 0 ; cnt < MaxIteNum ; ++cnt)
{
vec_size[cnt] = dist(rand_generator) ;
}
chrono::duration<double> duration_cost = static_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - start_tp ) ;
cout << "generate " << MaxIteNum << " random size , cost " << duration_cost.count() << " s." << endl ;
// Method 1 , just create in circulation
start_tp = chrono::high_resolution_clock::now() ;
for(size_t cnt = 0 ; cnt < MaxIteNum ; ++cnt)
{
vector<int> tmp_vec ;
int random_size = vec_size[cnt] ;
for(size_t idx = 0 ; idx < random_size ; ++idx)
tmp_vec.push_back(idx) ;
}
duration_cost = static_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - start_tp ) ;
cout << "Method 1 (ceate at every iteration and no previous space allocation) cost " << duration_cost.count() << " s."<< endl ;
// Method 2 , create in circulation , but do pre-allocation
start_tp = chrono::high_resolution_clock::now() ;
for(size_t cnt = 0 ; cnt < MaxIteNum ; ++cnt)
{
int random_size = vec_size[cnt] ;
vector<int> tmp_vec(random_size) ;
for(size_t idx = 0 ; idx < random_size ; ++idx)
tmp_vec[idx] = idx ;
}
duration_cost = static_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - start_tp ) ;
cout << "Method 2 (create at every iteration but use pre-allocation) cost " << duration_cost.count() << " s."<< endl ;
// Method 3 , create before circulation & do pre-allocation , but use `resize` to change size
start_tp = chrono::high_resolution_clock::now() ;
vector<int> tmp_vec4method3(666 , 0) ;
for(size_t cnt = 0 ; cnt < MaxIteNum ; ++cnt)
{
int random_size = vec_size[cnt] ;
tmp_vec4method3.resize(random_size) ;
for(size_t idx = 0 ; idx < random_size ; ++idx)
tmp_vec4method3[idx] = idx ;
}
duration_cost = static_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - start_tp ) ;
cout << "Method 3 (create before circulation & pre-allocatin & use `resize` to set size) cost " << duration_cost.count() << " s."<< endl ;
// Method 4 , create before circulation & do pre-allocation , use `clear` and push_back
start_tp = chrono::high_resolution_clock::now() ;
vector<int> tmp_vec4method4(666 , 0) ;
for(size_t cnt = 0 ; cnt < MaxIteNum ; ++cnt)
{
int random_size = vec_size[cnt] ;
tmp_vec4method4.clear() ;
for(size_t idx = 0 ; idx < random_size ; ++idx)
tmp_vec4method4.push_back(idx) ;
}
duration_cost = static_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - start_tp ) ;
cout << "Method 4 (create before circulation & pre-allocatin & use `clear` and `push_back`)" << duration_cost.count() << " s."<< endl ;
return 0 ;
}