当前位置:  开发笔记 > 编程语言 > 正文

在C++中使用数组或std :: vectors,性能差距是多少?

如何解决《在C++中使用数组或std::vectors,性能差距是多少?》经验,为你挑选了8个好方法。

在我们的C++课程中,他们建议不再在新项目中使用C++数组.据我所知,Stroustroup本人建议不要使用数组.但是有显着的性能差异吗?



1> Johannes Sch..:

new应避免使用C++数组(即使用动态数组).有问题你必须跟踪大小,你需要手动删除它们,并做所有类型的内务管理.

不鼓励在堆栈上使用数组,因为您没有范围检查,并且传递数组将丢失有关其大小的任何信息(数组到指针转换).boost::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 

struct S
{
  int padding;

  std::vector v;
  int * p;
  std::vector::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-allocated阵列可以具有性能优势,因为std::vector初始化所有元素在构造上的默认值(例如0表示int)(为了记住我而归功于@bernie).


谁发明了该死的AT&T语法?只有我知道...... :)
+1为*"索引向量与索引指针一样该死的."*以及其他结论.
我的答案中的一点是,向量不会*比相应的指针操作慢.当然,它*可以*(通过启用调试模式也很容易实现):)
对于Visual C++编译器,情况并非如此.但对于海湾合作委员会来说.
@ Piotr99我不打算与你争论,但是当你学习汇编_after_学习更高级别的语言时,英特尔语法比一些倒退,前缀(数字),后缀(指令)和模糊(访问内存)更有意义)AT&T语法的本质.
请注意,std :: tr1 :: array(或boost :: array)可以解决使用new的本机数组的情况.

2> paercebal..:

微优化器人的序言

记得:

"程序员浪费了大量的时间来考虑或担心程序中非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响.我们应该忘记效率低下,比如说97%的时间:过早优化是一切罪恶的根源.但我们不应该放弃我们在那个关键的3%中的机会".

(感谢完全引用的变态)

不要使用C数组而不是向量(或其他)只是因为你认为它更快,因为它应该是更低级别.你错了.

默认情况下使用矢量(或适合您需要的安全容器),然后如果您的探查器说它有问题,请查看您是否可以通过使用更好的算法或更改容器来优化它.

这说,我们可以回到最初的问题.

静态/动态阵列?

C++数组类比低级C数组表现得更好,因为它们对自己了解很多,并且可以回答C数组不能解决的问题.他们能够自己清理.更重要的是,它们通常使用模板和/或内联编写,这意味着调试中的许多代码看起来很容易解决在发布版本中产生的很少或没有代码,这意味着它们内置的安全性较低的竞争没有区别.

总而言之,它分为两类:

动态数组

使用指向malloc-ed/new-ed数组的指针最好与std :: vector版本一样快,并且安全性要低得多(参见litb的帖子).

所以使用std :: vector.

静态数组

使用静态数组充其量只是:

与std :: array版本一样快

而且安全性要低得多.

所以使用std :: array.

未初始化的内存

有时,使用vector而不是原始缓冲区会产生可见的成本,因为vector它将在构造时初始化缓冲区,而它替换的代码则没有,正如bernie在他的回答中所说的那样.

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


当你说"使用静态数组最好和boost :: array版本一样快"时,它表明你有多么偏向.它应该是另一个,Boost:数组最好像静态数组一样快.
始终显示完整的报价."程序员浪费了大量的时间来考虑或担心程序中非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响.我们应该忘记效率低下,比如说97%的时间:过早的优化是万恶之源.然而,我们不应该把关键的3%的机会放弃." 否则它就变成了毫无意义的声音.
@toto:这是一个误解:你应该把它读作"使用静态数组最好((和boost :: array版本一样快)&&(安全性要低很多))".我将编辑帖子以澄清这一点.顺便说一下,谢谢你怀疑的好处.

3> EvilTeach..:

矢量是引擎盖下的阵列.表现是一样的.

在一个可能遇到性能问题的地方,没有正确调整向量的大小.

当向量填充时,它将自行调整大小,这可能意味着一个新的数组分配,接着是n个拷贝构造函数,然后是大约n个析构函数调用,然后是数组删除.

如果你的构造/破坏是昂贵的,那么你最好将矢量设置为正确的大小.

有一种简单的方法可以证明这一点.创建一个简单的类,显示何时构造/销毁/复制/分配.创建这些东西的向量,并开始在向量的后端推送它们.当向量填充时,随着向量调整大小,将会有一系列活动.然后使用大小为预期元素数量的向量再次尝试.你会看到差异.


Pendantry:性能有相同的大O. std :: vector做了一点点簿记,大概花了很少的时间.OTOH,在滚动自己的动态数组时,你最终会做同样的簿记.
@bjhend那么gcc的`std :: vector`听起来不符合标准吗?我相信,该标准要求`矢量:: push_back`已摊销不变的复杂性,并在每个`push_back`增加1容量会你占reallocs后为n ^ 2的复杂性. - 假设`push_back`和`insert`的某种指数容量增加,未能"保留"将最多导致向量内容副本的因子增加.如果你没有"保留()",1.5指数向量生长因子意味着大约3倍的副本.
@bjhend你错了.该标准禁止指数级增长:§23.2.3第16段说"表101列出了为某些类型的序列容器而不是其他序列容器提供的操作.实现应为"容器"列中显示的所有容器类型提供这些操作,并且应实施这些措施,以便采取摊销的固定时间." (表101是带有push_back的表).现在请停止传播FUD.没有主流实施违反了这一要求.微软的标准C++库增长了1.5倍,GCC增长了2倍.

4> Frank Kruege..:

要回应Mehrdad所说的话:

但是,可能存在您仍需要数组的情况.当与低级代码(即程序集)或需要数组的旧库接口时,您可能无法使用向量.

根本不是真的.如果使用的话,向量会很好地降级为数组/指针:

vector vector;
vector.push_back(42);

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

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

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


标准的原始1998年文本确实没有要求,但2003年有一个附录来解决这个问题,所以标准确实涵盖了它.http://herbsutter.wordpress.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/
C++ 03明确指出`&v [n] ==&v [0] + n`是有效的,只要`n`在大小范围内.包含此语句的段落没有随C++ 11而改变.
为什么不只使用std :: vector :: data()?

5> Germán Diago..:

在C++ 11中使用普通数组的原因更少.

从最快到最慢,有3种阵列,具体取决于它们具有的功能(当然,即使列表中的情况3,实现的质量也可以使事情变得非常快):

    在编译时已知大小的静态.---std::array

    动态,在运行时已知大小,从未调整大小.这里的典型优化是,如果可以直接在堆栈中分配数组.- 不可用.也许dynarray在C++ 14之后的C++ TS中.在C中有VLA

    在运行时动态和可调整大小.---std::vector

对于1.具有固定数量的元素纯静态数组,使用std::array在C++ 11.

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

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

使用提到的标准库类型来避免 数组衰减到指针.您将节省调试时间和性能完全相同,如果你使用相同的功能集相同,与普通的数组.


std :: dynarray.在审查了国家机构对n3690的评论之后,这个库组件从C++ 14工作文件中投入了单独的技术规范.此容器不是n3797中C++ 14草案的一部分.来自http://en.cppreference.com/w/cpp/container/dynarray

6> John D. Cook..:

跟STL一起去.没有性能损失.这些算法非常有效,它们可以很好地处理我们大多数人都不会想到的细节.



7> Mehrdad Afsh..:

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


由于向量是连续的,因此与需要数组的库进行交互仍然非常容易.
只有一种情况我知道哪些向量不能使用:如果大小为0.那么&a [0]或&*a.begin()将不起作用.c ++ 1x将通过引入一个a.data()函数来修复它,该函数返回保留元素的内部缓冲区

8> lalebarde..:

关于杜丽的贡献.

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

推荐阅读
小妖694_807
这个屌丝很懒,什么也没留下!