在我们的C ++课程中,他们建议不再在新项目上使用C ++数组。据我所知,Stroustroup本人建议不要使用数组。但是,性能存在明显差异吗?
int main(int argc, const std::vector<string>& argv)
在我们的C ++课程中,他们建议不再在新项目上使用C ++数组。据我所知,Stroustroup本人建议不要使用数组。但是,性能存在明显差异吗?
int main(int argc, const std::vector<string>& argv)
Answers:
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
并分配非类对象(例如plain int
)或不使用用户定义的构造函数的类,并且您不希望初始初始化元素,则使用new
-allocated数组会带来性能优势,因为std::vector
将所有元素初始化为构造上的默认值(例如,int为0)(@ bernie提醒我)。
记得:
“程序员浪费大量时间来思考或担心程序的非关键部分的速度,而在考虑调试和维护时,这些对效率的尝试实际上会产生严重的负面影响。我们应该忘记效率低下,例如, 97%的时间:过早的优化是万恶之源。但是,我们不应该在这3%的临界风险中放弃机会。”
(感谢变态的完整报价)
不要仅仅使用C数组而不是向量(或其他任何向量),因为您认为它会更快,因为它应该是较低级别的。你会错的。
默认情况下使用矢量(或适合您需要的安全容器),然后,如果您的分析器认为这是一个问题,请查看是否可以通过使用更好的算法或更改容器来对其进行优化。
话虽如此,我们可以回到原来的问题。
C ++数组类比低级C数组的行为更好,因为它们对自己有很多了解,并且可以回答C数组无法回答的问题。他们能够自己清洗。更重要的是,它们通常是使用模板和/或内联语言编写的,这意味着在调试中许多代码中出现的内容可以解析为发行版本中生成的代码很少或根本没有生成,这意味着它们与内置的安全性较低的竞争没有区别。
总而言之,它分为两类:
使用指向malloc-ed / new-ed数组的指针最多和std :: vector版本一样快,而且安全性要差很多(请参阅litb的文章))。
因此,请使用std :: vector。
最好使用静态数组:
所以使用std :: array。
有时,使用a vector
代替原始缓冲区会产生明显的成本,因为vector
会在构造时初始化缓冲区,而替换的代码却没有初始化,正如bernie在回答中指出的那样。。
如果是这种情况,那么您可以使用a unique_ptr
而不是a 来处理它,vector
或者,如果这种情况在您的代码行中不是特殊情况,请实际编写一个buffer_owner
拥有该内存的类,并为您提供轻松,安全的访问权限,包括诸如调整大小(使用realloc
?)或任何您需要的奖金。
向量是幕后的数组。表现是一样的。
可能会遇到性能问题的一个地方是,开始时没有正确确定向量的大小。
当向量填充时,它将调整自身大小,这可能意味着要分配新的数组,然后是n个复制构造函数,然后是大约n个析构函数调用,然后是数组删除。
如果构造/销毁很昂贵,那么将向量设为正确的大小会更好。
有一个简单的方法可以证明这一点。创建一个简单的类,显示何时构造/销毁/复制/分配它。创建这些事物的向量,然后开始将它们推到向量的后端。当矢量填充时,随着矢量调整大小,将会有一系列的活动。然后使用向量大小调整为预期元素数的方式再次尝试。您将看到差异。
std::vector
声音听起来不符合标准?我相信该标准要求vector::push_back
摊销不变的复杂度,并且push_back
在考虑重新分配后,将每个容量的容量增加1 将是n ^ 2复杂度。-假设某种指数型增容push_back
和insert
,未能reserve
将导致至多在矢量内容拷贝常数因子增加。如果您未能达到1.5指数向量增长因子,则意味着约3倍的拷贝数reserve()
。
为了回应梅赫达德所说的话:
但是,在某些情况下您仍然需要数组。当与需要数组的低级代码(即程序集)或旧库接口时,您可能无法使用向量。
完全不对。如果使用以下向量,向量会很好地退化为数组/指针:
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中的情况3,实现的质量也可以使事情变得非常快):
std::array<T, N>
dynarray
在C ++ 14之后的C ++ TS中。在C中有VLAstd::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];
与之相比,优点是您可以调整其大小,并且不会像普通数组那样衰减到指针。
使用提到的标准库类型可以避免 数组衰减为指针。您将节省调试时间和性能完全相同,如果你使用相同的功能集相同,与普通的数组。
STL是一个高度优化的库。实际上,甚至建议在可能需要高性能的游戏中使用STL。数组太容易出错,无法在日常任务中使用。当今的编译器也非常聪明,可以真正使用STL生成出色的代码。如果您知道自己在做什么,STL通常可以提供必要的性能。例如,通过将向量初始化为所需大小(如果从一开始就知道),就可以基本实现阵列性能。但是,在某些情况下您仍然需要数组。当与需要数组的低级代码(即程序集)或旧库接口时,您可能无法使用向量。
vec.data()
用于数据和vec.size()
大小。就这么简单。
如果以调试模式编译软件,许多编译器将不会内联向量的访问器功能。在性能成为问题的情况下,这将使stl向量的实现慢得多。这也将使代码更易于调试,因为您可以在调试器中看到分配了多少内存。
在优化模式下,我希望stl向量接近数组的效率。这是因为现在已经内联了许多矢量方法。
std::vector
当您想要未初始化的缓冲区(例如,用作的目标memcpy()
)时,使用vs原始数组绝对会对性能产生影响。一个std::vector
将初始化使用默认构造函数的所有元素。原始数组不会。
接受参数(这是第三种形式)的构造函数的C ++规范指出:std:vector
count
从各种数据源构造一个新的容器,可以选择使用用户提供的分配器alloc。
3)用计数的默认插入的T实例构造容器。不进行任何复制。
复杂
2-3)线性计数
原始数组不会产生此初始化费用。
两者之间的性能差异在很大程度上取决于实现-如果您将实现不佳的std :: vector与最佳数组实现进行比较,则该数组将获胜,但将其转过来,vector将获胜...
只要您将Apple与Apple进行比较(数组和向量都具有固定数量的元素,或者两者都动态调整大小),只要您遵循STL编码惯例,我就会认为性能差异可以忽略不计。别忘了使用标准C ++容器还可以让您使用标准C ++库中的预滚动算法,并且大多数预编译算法的性能可能比自己构建的相同算法的平均实现要好。 。
就是说,恕我直言,矢量在带有调试STL的调试方案中胜出,因为大多数具有适当调试模式的STL实现至少可以突出/显示人们在使用标准容器时所犯的典型错误。
哦,别忘了数组和向量共享相同的内存布局,因此您可以使用向量将数据传递给需要基本数组的传统C或C ++代码。请记住,在这种情况下,大多数赌注都不可用,您将再次处理原始内存。
std::deque
可以使用。
如果不需要动态调整大小,则可以节省空间以节省内存(一个指针/ size_t)。而已。
我认为主要的关注点不是性能,而是安全。数组可能会导致很多错误(例如,考虑调整大小),向量可以为您省去很多麻烦。
向量使用的内存比数组多一点,因为它们包含数组的大小。它们还会增加程序的硬盘大小,并可能增加程序的内存占用量。这些增加很小,但是如果您使用的是嵌入式系统,可能会很重要。尽管这些差异很重要的大多数地方是您将使用C而不是C ++的地方。
以下简单测试:
与“对向量和数组/指针的基本索引,解引用和增量操作生成的汇编代码的比较”的结论相矛盾。
数组和向量之间必须有区别。测试说是这样...只要尝试一下,代码就在里面...
有时候,数组确实比向量更好。如果您总是要操纵固定长度的对象集,那么数组会更好。考虑以下代码片段:
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 ++)。
如果您使用矢量来表示多维行为,则会对性能造成影响。
要点是每个子向量都具有大小信息会产生少量开销,并且不必进行数据序列化(多维c数组也是如此)。缺少序列化可以提供比微优化更多的机会。如果您要处理多维数组,最好扩展std :: vector并滚动自己的get / set / resize bits函数。
假设使用固定长度的数组(例如,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[]
,那么似乎性能上就应该没有任何区别。