242

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

4

20 回答 20

211

应避免将 C++ 数组与new(即使用动态数组)一起使用。有一个问题是您必须跟踪大小,您需要手动删除它们并进行各种整理。

也不鼓励在堆栈上使用数组,因为您没有范围检查,并且传递数组将丢失有关其大小的任何信息(数组到指针的转换)。在这种情况下,您应该使用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 <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并分配非类对象(如 plain int)或没有用户定义构造函数的类,并且您不希望最初初始化元素,则使用new-allocated 数组可能具有性能优势,因为std::vector将所有元素初始化为构造的默认值(例如,0 表示 int)(感谢@bernie 提醒我)。

于 2008-12-19T17:41:03.127 回答
89

微优化人员的序言

记住:

“程序员会浪费大量时间去思考或担心程序中非关键部分的速度,而在考虑调试和维护时,这些提高效率的尝试实际上会产生强烈的负面影响。我们应该忘记小的效率,比如97% 的时间:过早的优化是万恶之源。但我们不应该放弃那关键的 3% 的机会”。

(感谢变形金刚的完整报价)

不要仅仅因为您认为它更快,因为它应该是较低级别的,就使用 C 数组而不是向量(或其他)。你会错的。

使用默认向量(或适合您需要的安全容器),然后如果您的分析器说这是一个问题,请查看您是否可以通过使用更好的算法或更改容器来优化它。

这就是说,我们可以回到最初的问题。

静态/动态数组?

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

总而言之,它分为两类:

动态数组

使用指向 malloc-ed/new-ed 数组的指针充其量与 std::vector 版本一样快,而且安全性要低得多(请参阅litb 的帖子)。

所以使用 std::vector。

静态数组

最好使用静态数组:

所以使用std::array

未初始化的内存

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

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

于 2008-12-20T17:35:54.963 回答
35

向量是引擎盖下的数组。性能是一样的。

您可能会遇到性能问题的一个地方是一开始​​就没有正确调整向量的大小。

当一个向量被填满时,它会调整自己的大小,这可能意味着一个新的数组分配,然后是 n 个复制构造函数,然后是大约 n 个析构函数调用,然后是数组删除。

如果您的构造/破坏很昂贵,那么最好使向量一开始就具有正确的大小。

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

于 2008-12-19T18:17:54.753 回答
31

回应Mehrdad所说的话:

但是,在某些情况下您仍然需要数组。在与需要数组的低级代码(即程序集)或旧库交互时,您可能无法使用向量。

一点也不真实。如果您使用,向量会很好地降级为数组/指针:

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

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

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

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

于 2008-12-19T17:47:43.210 回答
19

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

自然界中有 3 种数组,从最快到最慢,取决于它们所具有的特性(当然,即使对于列表中的案例 3,实现的质量也可以使事情变得非常快):

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

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

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

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

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

于 2013-10-29T13:22:48.807 回答
9

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

带有参数的构造函数的c++ 规范(它是第三种形式)指出:std:vectorcount

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

  1. 使用默认插入的计数 T 实例构造容器。不制作副本。

复杂

2-3) 计数线性

原始数组不会产生这种初始化成本。

请注意,使用自定义分配器,可以避免向量元素的“初始化”(即使用默认初始化而不是值初始化)。有关更多详细信息,请参阅这些问题:

于 2017-05-13T16:00:56.420 回答
8

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

于 2008-12-19T17:41:51.347 回答
6

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

于 2008-12-19T17:37:29.937 回答
6

关于杜力的贡献,用我自己的测量。

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

于 2012-04-19T17:12:24.993 回答
4

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

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

于 2008-12-19T20:04:35.383 回答
3

两者之间的性能差异在很大程度上取决于实现——如果你将一个糟糕实现的 std::vector 与一个最佳数组实现进行比较,那么数组会赢,但是把它转过来,向量就会赢……

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

也就是说,恕我直言,向量在带有调试 STL 的调试场景中获胜,因为大多数具有适当调试模式的 STL 实现至少可以突出/概括人们在使用标准容器时所犯的典型错误。

哦,别忘了数组和向量共享相同的内存布局,因此您可以使用向量将数据传递给需要基本数组的遗留 C 或 C++ 代码。但请记住,在这种情况下,大多数赌注都失败了,而且您又要处理原始内存了。

于 2008-12-19T17:45:09.580 回答
2

如果您使用向量来表示多维行为,则会影响性能。

2d+ 向量会导致性能下降吗?

要点是每个具有大小信息的子向量都有少量开销,并且不一定会有数据序列化(就像多维 c 数组一样)。这种缺乏序列化可以提供比微优化更多的机会。如果你在做多维数组,最好只扩展 std::vector 并滚动你自己的 get/set/resize bits 函数。

于 2019-03-03T21:58:59.280 回答
1

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

于 2008-12-19T17:41:36.700 回答
1

可能存在一些边缘情况,您可以在内联函数内的内联函数内进行向量访问,在这种情况下,您已经超出了编译器将内联的内容,它将强制执行函数调用。那将是如此罕见,以至于不值得担心-总的来说,我同意litb

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

于 2008-12-19T18:04:48.650 回答
1

有时数组确实比向量好。如果您总是在操作一组固定长度的对象,那么数组会更好。考虑以下代码片段:

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++)。

于 2008-12-19T18:07:53.713 回答
1

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

于 2008-12-19T18:08:16.303 回答
1

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

于 2009-05-11T21:37:02.603 回答
1

以下简单测试:

C++ Array vs Vector 性能测试说明

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

数组和向量之间必须有区别。测试说是这样……试试吧,代码就在那里……

于 2013-04-14T06:31:00.893 回答
1

对于固定长度数组,发布版本中的性能是相同的(与 vector<> 相比),但在调试版本中,根据我的经验(MS Visual Studio 2015,C++ 11),低级数组的优势是 20 倍。

因此,如果您(或您的同事)倾向于在您的数组使用中引入错误,那么支持 STL 的“节省时间调试”论点可能是有效的,但如果您的调试时间主要是在等待您的代码运行到您所需要的时间,则可能不是目前正在努力,以便您可以逐步完成它。

从事数字密集型代码的经验丰富的开发人员有时会属于第二组(尤其是如果他们使用向量 :))。

于 2021-03-25T07:11:51.440 回答
0

假设一个固定长度的数组(例如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[],那么性能似乎应该没有任何差异。

于 2018-12-07T20:18:42.553 回答