在我们的 C++ 课程中,他们建议不要再在新项目中使用 C++ 数组。据我所知,Stroustroup 本人建议不要使用数组。但是有显着的性能差异吗?
int main(int argc, const std::vector<string>& argv)
应避免使用带有 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 <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
在构造时将所有元素初始化为默认值(例如,0 表示 int)(感谢@bernie 提醒我)。
微优化人员的序言
记住:
“程序员会浪费大量时间去思考或担心程序中非关键部分的速度,而在考虑调试和维护时,这些提高效率的尝试实际上会产生强烈的负面影响。我们应该忘记小的效率,比如97% 的时间:过早的优化是万恶之源。但我们不应该放弃那关键的 3% 的机会”。
(感谢 metamorphosis 的完整报价)
不要仅仅因为您认为它更快,因为它应该是较低级别的,就使用 C 数组而不是向量(或其他)。你会错的。
使用默认向量(或适合您需要的安全容器),然后如果您的分析器说这是一个问题,请查看您是否可以通过使用更好的算法或更改容器来优化它。
这就是说,我们可以回到最初的问题。
静态/动态数组?
C++ 数组类比低级 C 数组表现得更好,因为它们对自己了解很多,并且可以回答 C 数组无法回答的问题。他们能够自己清洁。更重要的是,它们通常是使用模板和/或内联编写的,这意味着在调试中出现的大量代码解析为在发布构建中生成的代码很少或没有,这意味着与它们内置的不太安全的竞争没有区别。
总而言之,它分为两类:
动态数组
使用指向 malloc-ed/new-ed 数组的指针充其量与 std::vector 版本一样快,但安全性要低得多(请参阅 litb's post)。
所以使用 std::vector。
静态数组
最好使用静态数组:
与 std::array 版本一样快
而且安全性要低得多。
所以使用 std::array。
未初始化的内存
有时,使用 vector
代替原始缓冲区会产生可见的成本,因为 vector
将在构造时初始化缓冲区,而它替换的代码不会,正如他的 answer 中的 bernie 所指出的那样。
如果是这种情况,那么您可以使用 unique_ptr
而不是 vector
来处理它,或者,如果这种情况在您的代码行中并不例外,则实际上编写一个将拥有该内存的类 buffer_owner
,并给出您可以轻松安全地访问它,包括调整大小等奖励(使用 realloc
?),或任何您需要的东西。
向量是引擎盖下的数组。性能是一样的。
您可能会遇到性能问题的一个地方是一开始就没有正确调整向量的大小。
当一个向量被填满时,它会调整自己的大小,这可能意味着一个新的数组分配,然后是 n 个复制构造函数,然后是大约 n 个析构函数调用,然后是数组删除。
如果您的构造/破坏很昂贵,那么最好使向量一开始就具有正确的大小。
有一种简单的方法可以证明这一点。创建一个简单的类,显示它何时被构造/销毁/复制/分配。创建这些东西的向量,并开始将它们推到向量的后端。当向量填满时,随着向量调整大小,将会有一系列活动。然后使用大小为预期元素数量的向量再次尝试。你会看到不同之处。
std::vector
听起来不符合标准?我相信标准要求 vector::push_back
具有摊销常数复杂性,并且在考虑重新分配后,将每个 push_back
的容量增加 1 将是 n^2 复杂性。 -- 假设 push_back
和 insert
上的某种指数容量增加,reserve
失败最多将导致矢量内容副本的常数因子增加。如果您未能做到 reserve()
,则 1.5 指数向量增长因子将意味着大约 3 倍的副本。
回应 Mehrdad 所说的话:
但是,在某些情况下您仍然需要数组。在与需要数组的低级代码(即程序集)或旧库交互时,您可能无法使用向量。
一点也不真实。如果您使用,向量会很好地降级为数组/指针:
vector<double> vector;
vector.push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
这适用于所有主要的 STL 实现。在下一个标准中,它将被要求工作(即使它今天做得很好)。
&v[n] == &v[0] + n
有效,前提是 n
在大小范围内。包含此语句的段落在 C++11 中没有改变。
在 C++11 中使用普通数组的理由更少。
自然界中有 3 种数组,从最快到最慢,取决于它们所具有的特性(当然,即使对于列表中的案例 3,实现的质量也可以使事情变得非常快):
静态的,在编译时已知大小。 --- std::array
对于 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];
相比的优点是您可以调整它的大小并且它不会像普通数组那样衰减为指针。
使用提到的标准库类型来避免 arrays decaying to pointers。如果您使用相同的一组功能,您将节省调试时间,并且性能与普通数组完全相同相同。
当您需要 未初始化 缓冲区(例如,用作 memcpy()
的目标)时,使用 std::vector
与原始数组肯定会对性能产生影响。 std::vector
将使用默认构造函数初始化其所有元素。原始数组不会。
采用 count
参数(它是第三种形式)的 std:vector
构造函数的 c++ spec 状态:
`从各种数据源构造一个新容器,可选择使用用户提供的分配器 alloc。
使用默认插入的计数 T 实例构造容器。不制作副本。
复杂
2-3) 计数线性
原始数组不会产生这种初始化成本。
请注意,使用自定义分配器,可以避免向量元素的“初始化”(即使用默认初始化而不是值初始化)。有关更多详细信息,请参阅这些问题:
C++11 和 Boost.Container 下 vector::resize(size_type n) 的这种行为是否正确?
如何避免 std::vector<> 初始化其所有元素?
resize
重载,默认构造数据,而不是像所有普通方法那样构造值。
std::vector
将总是 值构造,在一些边缘情况下可能会有轻微的开销。在您引用的构造函数位中,向量值构造,尽管暗示它默认构造,这非常烦人。
使用 STL。没有性能损失。这些算法非常有效,它们在处理我们大多数人不会想到的各种细节方面做得很好。
STL 是一个高度优化的库。事实上,甚至建议在可能需要高性能的游戏中使用 STL。数组太容易出错,无法在日常任务中使用。今天的编译器也很聪明,真的可以用 STL 生成优秀的代码。如果您知道自己在做什么,STL 通常可以提供必要的性能。例如,通过将向量初始化为所需的大小(如果您从一开始就知道),您基本上可以实现数组性能。但是,在某些情况下您仍然需要数组。在与需要数组的低级代码(即程序集)或旧库交互时,您可能无法使用向量。
vec.data()
表示数据,vec.size()
表示大小。就这么容易。
如果在调试模式下编译软件,许多编译器不会内联向量的访问器函数。在性能有问题的情况下,这将使 stl 向量实现更慢。它还将使代码更易于调试,因为您可以在调试器中看到分配了多少内存。
在优化模式下,我希望 stl 向量接近数组的效率。这是因为许多向量方法现在是内联的。
两者之间的性能差异在很大程度上取决于实现——如果你将一个糟糕实现的 std::vector 与一个最佳数组实现进行比较,那么数组会赢,但是把它转过来,向量就会赢……
只要您将苹果与苹果进行比较(数组和向量都有固定数量的元素,或者都动态调整大小),只要您遵循 STL 编码实践,我认为性能差异可以忽略不计。不要忘记,使用标准 C++ 容器还允许您使用作为标准 C++ 库一部分的预滚动算法,并且它们中的大多数可能比您自己构建的相同算法的平均实现性能更好.
也就是说,恕我直言,向量在带有调试 STL 的调试场景中获胜,因为大多数具有适当调试模式的 STL 实现至少可以突出/概括人们在使用标准容器时所犯的典型错误。
哦,别忘了数组和向量共享相同的内存布局,因此您可以使用向量将数据传递给需要基本数组的遗留 C 或 C++ 代码。但请记住,在这种情况下,大多数赌注都失败了,而且您又要处理原始内存了。
std::deque
。
如果您使用向量来表示多维行为,则会影响性能。
Do 2d+ vectors cause a performance hit?
要点是每个具有大小信息的子向量都有少量开销,并且不一定会有数据序列化(就像多维 c 数组一样)。这种缺乏序列化可以提供比微优化更多的机会。如果你在做多维数组,最好只扩展 std::vector 并滚动你自己的 get/set/resize bits 函数。
如果不需要动态调整大小,则有保存容量的内存开销(一个指针/size_t)。而已。
可能存在一些边缘情况,您可以在内联函数内的内联函数内进行向量访问,在这种情况下,您已经超出了编译器将内联的内容,它将强制执行函数调用。这种情况非常罕见,不值得担心——总的来说,我同意 litb。
我很惊讶还没有人提到这一点——在它被证明是一个问题之前不要担心性能,然后再进行基准测试。
我认为主要关注的不是性能,而是安全性。您可能会在使用数组时犯很多错误(例如,考虑调整大小),而向量会为您省去很多麻烦。
向量使用的内存比数组多一点,因为它们包含数组的大小。它们还增加了程序的硬盘大小,可能还会增加程序的内存占用。这些增加很小,但如果您使用的是嵌入式系统,则可能很重要。尽管这些差异很重要的大多数地方都是您将使用 C 而不是 C++ 的地方。
以下简单测试:
C++ Array vs Vector performance test explanation
与“为向量和数组/指针的基本索引、取消引用和递增操作生成的汇编代码的比较”的结论相矛盾。
数组和向量之间必须有区别。测试说是这样……试试吧,代码就在那里……
有时数组确实比向量好。如果您总是在操作一组固定长度的对象,那么数组会更好。考虑以下代码片段:
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];}
};
的数组版本是:
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<> 相比),但在调试版本中,根据我的经验(MS Visual Studio 2015,C++ 11),低级数组的优势是 20 倍。
因此,如果您(或您的同事)倾向于在您的数组使用中引入错误,则支持 STL 的“节省时间调试”论点可能是有效的,但如果您的调试时间主要是在等待您的代码运行到您所需要的点,则可能不是目前正在努力,以便您可以逐步完成它。
从事数字密集型代码的经验丰富的开发人员有时会属于第二组(尤其是如果他们使用向量 :))。
假设一个固定长度的数组(例如 int* v = new int[1000];
与 std::vector<int> v(1000);
,v
的大小固定为 1000),唯一真正重要的性能考虑因素(或者至少在我处于类似情况时对我很重要) dilemma) 是访问元素的速度。我查找了 STL 的矢量代码,发现如下:
const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }
这个函数肯定会被编译器内联。因此,只要您计划对 v
做的唯一事情是使用 operator[]
访问其元素,那么性能似乎应该没有任何差异。
不定期副业成功案例分享