C++ 迭代器范围向量构造还是向量擦除更快?
考虑从前面删除C++ 迭代器范围向量构造还是向量擦除更快?,c++,C++,考虑从前面删除x元素的函数的这两种不同实现: template <typename T> std::vector<T> drop(int size, const std::vector<T>& coll){ if (size<0) return std::vector<T>(); auto sized = size > coll.size() ? coll.size() : size; typename
x
元素的函数的这两种不同实现:
template <typename T>
std::vector<T> drop(int size, const std::vector<T>& coll){
if (size<0) return std::vector<T>();
auto sized = size > coll.size() ? coll.size() : size;
typename std::vector<T>::const_iterator first = coll.begin()+sized;
typename std::vector<T>::const_iterator last = coll.end();
return std::vector<T>(first,last);
}
template <typename T>
std::vector<T> drop2(int size, std::vector<T> coll){
if (size<0) return std::vector<T>();
auto sized = size > coll.size() ? coll.size() : size;
coll.erase(coll.begin(),coll.begin()+sized);
return coll;
}
输出:
dropExample(drop<int>);
dropExample(drop2<int>);
drop example
Delta t2-t1: 625 ms
drop example
Delta t2-t1: 346 ms
然后大体上:
int main(int argc, const char * argv[]) {
auto testrange=range(100000);
dropExample(drop<int>);
dropExample(drop2<int>);
dropExample2(drop<int>,testrange);
dropExample2(drop2<int>,testrange);
return 0;
}
以下是示例中使用的补充功能:
std::vector<int> range(int start, int end, int step);
std::vector<int> range(int start, int end){
if (end<start){
return range(start,end,-1);
}else if (start == end){
return std::vector<int> {start};
}else{
std::vector<int> nums(end-start);
std::iota(nums.begin(),nums.end(),start);
return nums;}
}
std::vector<int> range(int end){
return range(0,end);
}
std::vector<int> range(int start, int end, int step){
std::vector<int> nums{start};
auto next=start+step;
while ((next<end&&start<=end&&step>0)||
(next>end&&start>end&&step<0))
{
nums.push_back(next);
next+=step;
}
return nums;
}
std::向量范围(int开始、int结束、int步);
标准::矢量范围(整数开始,整数结束){
如果(endend&&step是真的,那么答案在于两个版本的广泛基准测试
然而,做一个评估,我倾向于相信第一个版本会更快,因为在一般情况下,从初始向量复制到输出向量的元素要比在第二个版本中复制所有元素的要少。第二个示例将创建一整组对象(在复制输入参数时)只是为了稍后(在调用擦除时)消除它们。因此产生的性能差异将取决于T
是什么,但我怀疑第一个参数会更慢。
此外,由于擦除不会重新分配内存,第二个版本中使用的内存量将更大
编辑
您当前的测试存在缺陷,因为您将要子集的向量作为临时传递,允许编译器在drop2
中移动并构造输入参数,从而完全删除副本。只需更改:
for (auto x: range(100000))
f(200, range(10000));
到
结果发生了很大的变化。但是,第二种方法对我来说速度更快,直到向量变得更大。还值得注意的是,因为您使用的是int
,不同的方法可以优化为memcpy
和一些指针操作
drop
可以简单地成为(coll.size()-size)*sizeof(int)
字节的memcpy,而drop2
可以成为coll.size()*sizeof(int)字节的memcpy
字节。这是因为int
的析构函数是不可操作的,因此对擦除的调用可能变成从向量的\u最后一个指针中减去大小
如果您只对这样的基本类型感兴趣,那也没关系,但是如果您还想为,std::string
提供一个最佳的实现,那么它的析构函数和复制构造函数就成为了非常重要的因素。我试过将std::vector
作为向量中的类型,虽然整体速度较慢,但f或者更小的尺寸看起来drop2
更快。drop
在更低的阈值下变得更有效。我非常怀疑这就是我们在这里看到的,所以我们最终运行的代码处于某种介于memcpy
和我们逐字编写的代码之间的状态
我想最终我们是在测试编译器优化不同函数的能力(std::uninitialized_copy
,std::move
(基于迭代器的函数),调用get_allocator().destroy(p)
在一个循环中对平凡和非平凡类型进行销毁,等等。)在这一点上,我所能说的是,即使代码中看似很小的更改,在优化的内容和优化的程度方面,结果也会有很大的差异
然而,我仍然感到惊讶的是drop2
的运行速度比drop
快,即使只是在一定大小的范围内。第一个几乎肯定更快,除非你输入drop
一个右值,在这种情况下你必须测量
假设有N个元素要开始,M个元素要删除:
- 第一个复制N-M个元素;第二个复制N个元素
- 第一个不执行赋值;第二个执行N-M个移动赋值,这可能会退化为复制赋值
- 第一种可能是,实际上也会是,RVO。第二种需要一种无法忽略的移动。然而,移动向量足够便宜,额外成本可能最小
第一种方法几乎肯定比第二种方法快。我认为使用分析器分析实际性能数据,而不仅仅是猜测是有意义的。不保证会使用RVO。编译器可能会这样做,但在编译并找到答案之前,您只能知道这些。对于测试示例,提供的vector是一个临时变量,函数直接在提供的向量上调用erase
。由于erase可能是一组移动操作,也可能是复制操作,所以速度会更快。请尝试提供一个l值。因为您处理的是int
,所以它只是复制操作。但是第二个测试不需要分配内存y、 什么是range
和logit
?我们需要一个可编译的示例。我更新了一个测试结果,表明第二个实际上要快得多。在我的测试示例中对测试设置进行了大量处理后,我终于能够创建第一个更快的场景,只有当向量在pre上操作时-下降的大小为1000万个元素。但是,只有当向量为左值时,第一个元素的下降速度才更快。第二个元素在所有其他测试中都保持最快的速度,不管大小如何。
drop example
Delta t2-t1: 564 ms
drop example
Delta t2-t1: 375 ms
drop example 2
Delta t2-t1: 2318 ms
drop example 2
Delta t2-t1: 698 ms
std::vector<int> range(int start, int end, int step);
std::vector<int> range(int start, int end){
if (end<start){
return range(start,end,-1);
}else if (start == end){
return std::vector<int> {start};
}else{
std::vector<int> nums(end-start);
std::iota(nums.begin(),nums.end(),start);
return nums;}
}
std::vector<int> range(int end){
return range(0,end);
}
std::vector<int> range(int start, int end, int step){
std::vector<int> nums{start};
auto next=start+step;
while ((next<end&&start<=end&&step>0)||
(next>end&&start>end&&step<0))
{
nums.push_back(next);
next+=step;
}
return nums;
}
for (auto x: range(100000))
f(200, range(10000));
auto v = range(10000);
for (auto x: range(100000))
f(200, v);