使用数组或std::vector在c++,什么's性能差距?

在我们的c++课程中,他们建议不要再在新项目中使用c++数组。据我所知,Stroustroup本人建议不要使用数组。但是否存在显著的性能差异?

217910 次浏览

STL是一个经过大量优化的库。事实上,甚至有人建议在需要高性能的游戏中使用STL。在日常任务中使用数组太容易出错。今天的编译器也非常聪明,可以用STL生成优秀的代码。如果您知道自己在做什么,STL通常可以提供必要的性能。例如,通过初始化向量到所需的大小(如果你从一开始就知道),你基本上可以实现数组的性能。但是,可能在某些情况下仍然需要数组。当与低级代码(即程序集)或需要数组的旧库进行接口时,您可能无法使用向量。

应该避免对new使用c++数组(即使用动态数组)。这里有一个问题,你必须跟踪大小,你需要手动删除它们,做各种各样的家务。

在堆栈上使用数组也是不鼓励的,因为您没有范围检查,并且传递数组将丢失关于其大小的任何信息(数组到指针的转换)。在这种情况下,应该使用std::array,它将c++数组包装在一个小类中,并提供size函数和迭代器来遍历它。

现在,std::vector与原生c++数组(来自互联网):

// Comparison of assembly code generated for basic indexing, dereferencing,
// and increment operations on vectors and arrays/pointers.


// Assembly code was generated by gcc 4.1.0 invoked with  g++ -O3 -S  on a
// x86_64-suse-linux machine.


#include <vector>


struct S
{
int padding;


std::vector<int> v;
int * p;
std::vector<int>::iterator i;
};


int pointer_index (S & s) { return s.p[3]; }
// movq    32(%rdi), %rax
// movl    12(%rax), %eax
// ret


int vector_index (S & s) { return s.v[3]; }
// movq    8(%rdi), %rax
// movl    12(%rax), %eax
// ret


// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.


int pointer_deref (S & s) { return *s.p; }
// movq    32(%rdi), %rax
// movl    (%rax), %eax
// ret


int iterator_deref (S & s) { return *s.i; }
// movq    40(%rdi), %rax
// movl    (%rax), %eax
// ret


// Conclusion: Dereferencing a vector iterator is the same damn thing
// as dereferencing a pointer.


void pointer_increment (S & s) { ++s.p; }
// addq    $4, 32(%rdi)
// ret


void iterator_increment (S & s) { ++s.i; }
// addq    $4, 40(%rdi)
// ret


// Conclusion: Incrementing a vector iterator is the same damn thing as
// incrementing a pointer.

注意:如果你用new分配数组,并分配非类对象(如普通的int)或没有用户定义构造函数而且的类,你不想让元素初始化,使用new分配的数组可以有性能优势,因为std::vector在构造时将所有元素初始化为默认值(例如,int为0)(感谢@bernie提醒我)。

如果不需要动态调整大小,则会有存储容量的内存开销(一个指针/size_t)。就是这样。

选择STL。没有性能损失。这些算法非常高效,它们在处理我们大多数人不会想到的细节方面做得很好。

两者之间的性能差异很大程度上取决于实现——如果你比较一个实现得很差的std::vector和一个优化的数组实现,数组会赢,但是反过来,vector会赢……

只要你比较苹果和苹果(数组和向量都有固定数量的元素,或者都动态地调整大小),我认为性能差异是可以忽略不计的,只要你遵循STL编码实践。不要忘记,使用标准c++容器还允许您使用作为标准c++库一部分的预滚动算法,并且它们中的大多数可能比您自己构建的相同算法的平均实现性能更好。

也就是说,以我之见,vector在使用调试STL的调试场景中胜出,因为大多数具有适当调试模式的STL实现至少可以突出/清除人们在使用标准容器时所犯的典型错误。

哦,不要忘记数组和vector共享相同的内存布局,因此您可以使用vector将数据传递给需要基本数组的遗留C或c++代码。但是,请记住,在这种情况下,大多数赌注都是无效的,您将再次处理原始内存。

人士说的事情做出回应:

然而,在某些情况下 你仍然需要数组。当 与低级代码(例如: 程序集)或旧的库 需要数组,您可能无法

完全不是这样的。向量可以很好地降级为数组/指针,如果你使用:

vector<double> vector;
vector.push_back(42);


double *array = &(*vector.begin());


// pass the array to whatever low-level code you have

这适用于所有主要的STL实现。在下一个标准中,它将被要求工作(即使它现在做得很好)。

可能会有一些边缘情况,你在内联函数中有一个向量访问在内联函数中,你已经超出了编译器将内联的范围,它将强制函数调用。这将是如此罕见,以至于不值得担心-一般来说,我同意litb

我很惊讶居然没有人提到这一点——不要担心性能,直到它被证明是一个问题,然后进行基准测试。

有时候数组确实比向量好。如果你总是在操纵别人 固定长度的对象集合,数组更好。

int main() {
int v[3];
v[0]=1; v[1]=2;v[2]=3;
int sum;
int starttime=time(NULL);
cout << starttime << endl;
for (int i=0;i<50000;i++)
for (int j=0;j<10000;j++) {
X x(v);
sum+=x.first();
}
int endtime=time(NULL);
cout << endtime << endl;
cout << endtime - starttime << endl;


}

X的向量在哪里

class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};

X的数组版本为:

class X {
int f[3];


public:
X(int a[]) {f[0]=a[0]; f[1]=a[1];f[2]=a[2];}
int first() { return f[0];}
};

main()的数组版本将更快,因为我们避免了

.每次在内循环中"new"的开销

(此代码由我发布到comp.lang.c++)。

我认为最主要的问题不是性能,而是安全性。使用数组可能会犯很多错误(例如,考虑调整大小),而使用向量可以省去很多麻烦。

向量是底层的数组。

一个可能会遇到性能问题的地方是,vector的大小一开始就不正确。

当一个vector容器被填充时,它将调整自身的大小,这可能意味着,一个新的数组分配,然后是n个复制构造函数,然后是大约n个析构函数调用,然后是一个数组删除。

如果你的构造/销毁是昂贵的,你最好让向量的正确大小开始。

有一种简单的方法可以证明这一点。创建一个简单的类,显示它何时被构造/销毁/复制/赋值。创建一个这些东西的向量,并开始将它们推到向量的后端。当向量被填充时,随着向量大小的调整,将会有一连串的活动。然后再试一次,将向量大小调整为预期的元素数量。你会发现其中的不同。

如果在调试模式下编译软件,许多编译器将不会内联vector的访问器函数。这将使stl向量的实现在性能有问题的情况下变得更慢。它还将使代码更容易调试,因为您可以在调试器中看到分配了多少内存。

在优化模式下,我希望stl向量接近数组的效率。这是因为许多vector方法现在都内联了。

为微优化人员准备的序言

记住:

程序员浪费了大量的时间去思考或担心程序中非关键部分的速度,而当考虑到调试和维护时,这些追求效率的尝试实际上会产生强烈的负面影响。我们应该忘记小的效率,大约97%的时间:过早的优化是万恶之源。然而,我们不应该错过我们的机会在关键的3%"

(感谢蜕变提供完整的引用)

不要使用C数组来代替向量(或任何东西),因为你认为它更快,因为它应该是低级别的。你可能错了。

使用默认的向量(或者根据需要使用安全的容器),然后如果你的分析器说这是一个问题,看看你是否可以优化它,要么使用更好的算法,要么改变容器。

话虽如此,我们可以回到最初的问题。

静态/动态数组?

c++数组类比低级C数组表现得更好,因为它们了解自己很多东西,并且可以回答C数组不能回答的问题。他们能够自己打扫卫生。更重要的是,它们通常是使用模板和/或内联编写的,这意味着在调试中出现的大量代码在发布版本中分解为很少或没有代码,这意味着它们与内置的不太安全的竞争没有区别。

总而言之,它分为两类:

动态数组

使用指向malloc-ed/new-ed数组的指针最多和std::vector版本一样快,但安全性要低得多(参见litb的文章)。

所以使用std::vector。

静态数组

最好使用静态数组:

所以使用std::数组

未初始化的内存

有时,使用vector而不是原始缓冲区会导致明显的代价,因为vector将在构造时初始化缓冲区,而它所取代的代码没有,正如伯尼 by在他的回答中所述。

如果是这种情况,那么你可以使用unique_ptr而不是vector来处理它,或者,如果这种情况在你的代码线中不是特殊情况,实际上编写一个类buffer_owner来拥有该内存,并让你轻松安全地访问它,包括调整它的大小(使用realloc?)或任何你需要的奖励。

向量使用的内存比数组多一点,因为它们包含数组的大小。它们还会增加程序的硬盘大小,可能还会增加程序的内存占用。这些增加很小,但如果您使用的是嵌入式系统,则可能很重要。尽管这些差异很重要的大多数地方都是使用C而不是c++的地方。

关于杜里的贡献与我自己的测量。

结论是整数数组比整数向量快(在我的例子中是5倍)。然而,对于更复杂/未对齐的数据,数组和向量的速度大致相同。

下面是简单的测试:

c++数组vs矢量性能测试解释 .

与“对向量和数组/指针的基本索引、解引用和增量操作生成的汇编代码的比较”的结论相矛盾。

数组和向量之间一定有区别。测试是这么说的…试试吧,代码就在那里……

在c++ 11中使用普通数组的理由就更少了。

从最快到最慢,本质上有3种类型的数组,这取决于它们所具有的特性(当然,实现的质量可以使事情变得非常快,即使是列表中的情况3):

  1. 静态的,在编译时大小已知。——std::array<T, N>
  2. 动态的,运行时大小已知,从不调整大小。这里的典型优化是,如果数组可以直接分配到堆栈中。——不可用。可能在c++ 14之后的c++ TS中dynarray。在C中有vla
  3. 动态的,可在运行时调整大小。——std::vector<T>

对于元素数量固定的1.普通静态数组,在c++ 11中使用std::array<T, N>

对于在运行时指定的2.固定大小数组,但这不会改变它们的大小,在c++ 14中有讨论,但它已被转移到技术规范,最终由c++ 14制作。

对于3. std::vector<T> 通常会在堆中请求内存。这可能会影响性能,不过你可以使用std::vector<T, MyAlloc<T>>自定义分配器来改善这种情况。与T mytype[] = new MyType[n];相比,它的优点是可以调整它的大小,并且它不会像普通数组那样衰减为指针。

使用上面提到的标准库类型来避免数组衰减为指针。如果使用相同的特性集,将节省调试时间,并且性能与普通数组相同。

当你想要未初始化缓冲区(例如用作memcpy()的目的地)时,使用std::vector与使用原始数组肯定会有性能影响。std::vector将使用默认构造函数初始化其所有元素。原始数组则不会。

std:vector构造函数的c++规范接受count实参(这是第三种形式)声明:

从各种数据源构造一个新容器,可选地使用用户提供的分配器alloc。

  1. 使用默认插入的t的count个实例构造容器。

复杂性

2-3)计数线性

原始数组不会产生这种初始化代价。

注意,使用自定义分配器,可以避免“初始化”;向量元素的初始化(即使用默认初始化而不是值初始化)。请看这些问题了解更多细节:

假设一个固定长度的数组(例如int* v = new int[1000]; vs std::vector<int> v(1000);,其中v的大小被固定为1000),唯一真正重要的性能考虑因素(至少当我处于类似的困境时对我来说很重要)是访问元素的速度。我查了一下STL的向量代码,下面是我的发现:

const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }

这个函数肯定会被编译器内联。因此,只要你计划用v做的唯一一件事是用operator[]访问它的元素,似乎在性能上不应该有任何差别。

对于固定长度的数组,在发布版本中的性能是相同的(相对于vector<>),但在调试版本中,低级数组以我的经验胜出20倍(MS Visual Studio 2015, c++ 11)。

所以“节省调试时间”;如果你(或你的同事)倾向于在数组使用中引入错误,那么支持STL的论点可能是有效的,但如果你的调试时间主要是等待你的代码运行到你当前正在工作的位置,这样你就可以逐步检查它,那么可能就不是这样了。

处理数字密集型代码的有经验的开发人员有时属于第二组(特别是如果他们使用vector:))。

它们中哪一个是最好的或好用的,没有争论。它们都有自己的用例,它们都有自己的优点和缺点,这两种容器在不同的地方的行为是不同的。数组的主要困难之一是它们的大小是固定的,一旦它们被定义或初始化,那么你就不能改变值,而另一方面向量是灵活的,你可以随时改变向量的值,它不像数组一样大小固定,因为数组有静态内存分配,而向量有动态内存或堆内存分配(我们可以将元素推入或从向量中取出),c++的创建者Bjarne Stroustrup说向量比数组更灵活。

应该避免使用带有new的c++数组(即使用动态数组)。有一个问题是,你必须跟踪它们的大小,你需要手动删除它们,并做所有的家务工作。

我们还可以很容易地在向量中插入、推入和拉出值,这在数组中是不容易做到的。

如果我们讨论性能,那么如果你处理小的值,那么你应该使用数组,如果你处理大规模的代码,那么你应该使用向量(向量比数组更擅长处理大的值)。