std::vector比普通数组慢很多吗?

我一直认为这是普遍的智慧,std::vector是“实现为一个数组,”等等等等。今天我去测试了一下,结果似乎不是这样:

以下是一些测试结果:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

这大约要慢3 - 4倍!并不能证明“vector可能会慢几纳秒”的注释是正确的。

我使用的代码是:

#include <cstdlib>
#include <vector>


#include <iostream>
#include <string>


#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>


class TestTimer
{
public:
TestTimer(const std::string & name) : name(name),
start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
{
}


~TestTimer()
{
using namespace std;
using namespace boost;


posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
posix_time::time_duration d = now - start;


cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
" seconds" << endl;
}


private:
std::string name;
boost::posix_time::ptime start;
};


struct Pixel
{
Pixel()
{
}


Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
{
}


unsigned char r, g, b;
};


void UseVector()
{
TestTimer t("UseVector");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel> pixels;
pixels.resize(dimension * dimension);


for(int i = 0; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
}


void UseVectorPushBack()
{
TestTimer t("UseVectorPushBack");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel> pixels;
pixels.reserve(dimension * dimension);


for(int i = 0; i < dimension * dimension; ++i)
pixels.push_back(Pixel(255, 0, 0));
}
}


void UseArray()
{
TestTimer t("UseArray");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);


for(int i = 0 ; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}


free(pixels);
}
}


int main()
{
TestTimer t1("The whole thing");


UseArray();
UseVector();
UseVectorPushBack();


return 0;
}

我做错了吗?还是我刚刚打破了这个性能神话?

我在Visual  Studio  2005年中使用Release模式。


Visual c++中,#define _SECURE_SCL 0UseVector缩短了一半(缩短到4秒)。在我看来,这真的是件大事。

111643 次浏览

GNU的STL(和其他),给定vector<T>(n),默认构造一个原型对象T() -编译器将优化空构造函数-但随后STL的__uninitialized_fill_n_aux将获取内存地址中现在为该对象保留的任何垃圾的副本,循环填充该对象的副本作为vector中的默认值。因此,“my”STL不是循环构造,而是构造然后循环/复制。这是反直觉的,但我应该记得,因为我评论了最近的stackoverflow问题关于这一点:构造/复制可以更有效的引用计数对象等。

所以:

vector<T> x(n);

vector<T> x;
x.resize(n);

是-在许多STL实现中-类似于:

T temp;
for (int i = 0; i < n; ++i)
x[i] = temp;

问题是,当前一代的编译器优化器似乎并没有从temp是未初始化的垃圾的洞察中工作,并且未能优化出循环和默认的复制构造函数调用。你可能会认为编译器绝对不应该优化它,因为编写上述代码的程序员有一个合理的期望,即在循环之后所有对象都是相同的,即使是垃圾(通常关于' same '/operator== vs memcmp/operator= etc适用的警告)。编译器不能期望对std::vector<>的更大的上下文有任何额外的洞察,或者对数据的后续使用表明这种优化是安全的。

这可以与更明显的直接实现形成对比:

for (int i = 0; i < n; ++i)
x[i] = T();

我们可以期待一个编译器优化。

为了更明确地解释vector行为的这一方面,可以考虑:

std::vector<big_reference_counted_object> x(10000);

显然,如果我们创建10000个独立对象,而不是创建10000个引用相同数据的对象,这是一个很大的区别。有一种合理的观点认为,保护普通c++用户不意外地做一些如此昂贵的事情的好处超过了现实世界中难以优化的拷贝构造的非常小的成本。

原始答案(供参考/理解评论): 没有机会。Vector和数组一样快,至少如果你合理地预留空间. ...

尝试禁用检查迭代器并在发布模式下构建。您应该不会看到太大的性能差异。

下面是vector中的push_back方法的工作原理:

  1. vector在初始化时分配X个空间。
  2. 如下所述,它检查当前底层数组中是否有空间用于该项。
  3. 它复制push_back调用中的项。

调用push_back X项后:

  1. vector将kX的空间重新分配到第二个数组中。
  2. 它将第一个数组的项复制到第二个数组。
  3. 丢弃第一个数组。
  4. 现在使用第二个数组作为存储,直到它达到kX项。

重复。如果你不是reserving空间,它肯定会更慢。更重要的是,如果复制项目的成本很高,那么像这样的“push_back”会让你生吞活剥。

至于vector vs . array的事情,我将不得不同意其他人的观点。在发布版中运行,打开优化,并放入更多的标志,这样微软的友好人员就不会为你而烦恼了。

还有一件事,如果你不需要调整大小,使用Boost.Array。

使用以下方法:

g++ -O3 Time.cpp -I <MyBoost> . / a.o ut < br > UseArray完成在2.196秒
UseVector完成在4.412秒
UseVectorPushBack完成在8.017秒
整个过程只用了14.626秒

数组的速度是向量的两倍。

在更详细地查看代码后,这是预期的;当你遍历向量两次,只遍历数组一次时。注意:当你resize() vector时,你不仅是在分配内存,而且还在遍历vector并调用每个成员的构造函数。

稍微重新排列代码,使vector只初始化每个对象一次:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

现在再做一次同样的计时:

g++ -O3 Time.cpp -I <MyBoost> . / a.o ut < br > UseVector完成在2.216秒

vector现在的性能只比数组差一点点。在我看来,这种差异是微不足道的,可能是由一大堆与测试无关的事情造成的。

我还会考虑到你没有正确地初始化/销毁UseArrray()方法中的Pixel对象,因为没有调用构造函数/析构函数(这对于这个简单的类可能不是问题,但任何稍微复杂一点的(例如指针或指针成员)都会引起问题。

向量类还调用Pixel构造函数。

每一种都会导致你在计时时运行近一百万次。

编辑:然后是外层…1000个循环,所以要做十亿次ctor调用!

编辑2:看到UseArray案例的分解会很有趣。优化器可以优化整个事情,因为它除了消耗CPU外没有其他效果。

好吧,因为vector::resize()比普通内存分配(由malloc)做更多的处理。

尝试在复制构造函数中设置断点(定义它以便可以设置断点!),就会增加处理时间。

好问题。我来这里是希望能找到一些简单的方法来加快矢量测试的速度。结果跟我想象的不太一样!

优化有帮助,但这还不够。通过优化,我仍然看到UseArray和UseVector之间的2X性能差异。有趣的是,UseVector明显比没有优化的UseVectorPushBack慢。

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

想法1 -使用new[]代替malloc

我尝试在UseArray中将malloc()更改为new[],以便构造对象。从单个字段分配到分配一个Pixel实例。哦,重命名内部循环变量为j

void UseArray()
{
TestTimer t("UseArray");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


// Same speed as malloc().
Pixel * pixels = new Pixel[dimension * dimension];


for(int j = 0 ; j < dimension * dimension; ++j)
pixels[j] = Pixel(255, 0, 0);


delete[] pixels;
}
}

令人惊讶的是(对我来说),这些变化没有任何不同。甚至没有改变new[],这将默认构造所有的像素。似乎gcc在使用new[]时可以优化默认构造函数调用,但在使用vector时则不行。

想法#2 -删除重复的操作符[]调用

我还尝试摆脱三重operator[]查找并缓存对pixels[j]的引用。这实际上降低了UseVector的速度!哦。

for(int j = 0; j < dimension * dimension; ++j)
{
// Slower than accessing pixels[j] three times.
Pixel &pixel = pixels[j];
pixel.r = 255;
pixel.g = 0;
pixel.b = 0;
}


# ./vector
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

想法#3 -删除构造函数

如果完全删除构造函数呢?然后,也许gcc可以在创建向量时优化所有对象的结构。如果我们把像素改为:

struct Pixel
{
unsigned char r, g, b;
};

结果:大约快10%。还是比数组慢。嗯。

# ./vector
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

想法4 -使用迭代器而不是循环索引

vector<Pixel>::iterator代替循环索引怎么样?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
j->r = 255;
j->g = 0;
j->b = 0;
}

结果:

# ./vector
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

没有什么不同。至少没有变慢。我认为这将具有类似于#2的性能,其中我使用了Pixel&引用。

结论

即使一些聪明的cookie想出了如何使vector循环和数组循环一样快,这也不能很好地说明std::vector的默认行为。编译器足够聪明,可以优化所有c++特性,并使STL容器像原始数组一样快。

底线是,当使用std::vector时,编译器无法优化掉无操作的默认构造函数调用。如果你使用普通的new[],它会很好地优化它们。但不是std::vector。即使你可以重写你的代码,以消除构造函数调用,在这里的咒语:“编译器比你聪明。STL和普通c一样快,不用担心。”

一些分析器数据(像素对齐为32位):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

废话

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
* Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
*
* 141008 52.5367
*/
--
* Total samples for file : "/home/andrey/libcchem/src/test.cpp"
*
*  61556 22.9345
*/
--
* Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
*
*  41956 15.6320
*/
--
* Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
*
*  20956  7.8078
*/
--
* Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
*
*   2923  1.0891
*/

allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
:      // 402. wrong new expression in [some_] allocator::construct
:      void
:      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
:{ /* UseVector() total:  60121 22.3999 */
...
:
:
10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
:
495  0.1844 :            pixels[i].r = 255;
:
12618  4.7012 :            pixels[i].g = 0;
:
2253  0.8394 :            pixels[i].b = 0;
:
:        }

数组

               :void UseArray()
:{ /* UseArray() total:  35191 13.1114 */
:
...
:
136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
:
9897  3.6874 :            pixels[i].r = 255;
:
3511  1.3081 :            pixels[i].g = 0;
:
21647  8.0652 :            pixels[i].b = 0;

大部分开销都在复制构造函数中。例如,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());


pixels.reserve(dimension * dimension);


for (int i = 0; i < dimension * dimension; ++i) {


pixels[i].r = 255;


pixels[i].g = 0;


pixels[i].b = 0;
}

它具有与数组相同的性能。

马丁·约克的回答困扰我,因为它似乎是一个试图刷下地毯的初始化问题。但他将冗余的默认构造确定为性能问题的根源是正确的。

[编辑:Martin的回答不再建议更改默认构造函数。]

对于眼前的问题,你当然可以调用vector<Pixel> ctor的2参数版本:

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

如果你想用一个常数值初始化,这是一种常见的情况。但更普遍的问题是:如何有效地初始化比常数值更复杂的东西?

为此,你可以使用back_insert_iterator,它是一个迭代器适配器。下面是一个向量为ints的例子,尽管一般思想也适用于Pixels:

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
squares() { i = 0; }
int operator()() const { ++i; return i * i; }


private:
int i;
};


...


std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

或者你可以使用copy()transform()来代替generate_n()

缺点是,构造初始值的逻辑需要移动到一个单独的类中,这比将其放在原位更不方便(尽管c++ 1x中的lambdas使这更好)。此外,我希望这仍然不会像基于__abc0的非stl版本那样快,但我希望它会接近,因为它只对每个元素进行一次构造。

公平地说,您不能将c++实现与C实现进行比较,即我所说的malloc版本。Malloc不创建对象——它只分配原始内存。然后不调用构造函数就把内存当作对象,这是拙劣的c++(可能是无效的——我把这个问题留给语言律师吧)。

也就是说,简单地将malloc更改为new Pixel[dimensions*dimensions],并将free更改为delete [] pixels,这与你所拥有的Pixel的简单实现没有太大区别。下面是我的盒子(E6600, 64位)上的结果:

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

但随着一个微小的变化,情况发生了变化:

Pixel.h

struct Pixel
{
Pixel();
Pixel(unsigned char r, unsigned char g, unsigned char b);


unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"


Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b)
: r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

编译如下:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

我们得到了非常不同的结果:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

使用Pixel的非内联构造函数,std::vector现在可以击败原始数组。

似乎通过std::vector和std:allocator进行分配的复杂性太大,无法像简单的new Pixel[n]那样有效地进行优化。然而,我们可以看到问题仅仅是分配而不是vector访问,通过调整几个测试函数来创建vector/数组,将其移到循环之外:

void UseVector()
{
TestTimer t("UseVector");


int dimension = 999;
std::vector<Pixel> pixels;
pixels.resize(dimension * dimension);


for(int i = 0; i < 1000; ++i)
{
for(int i = 0; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
}

而且

void UseArray()
{
TestTimer t("UseArray");


int dimension = 999;
Pixel * pixels = new Pixel[dimension * dimension];


for(int i = 0; i < 1000; ++i)
{
for(int i = 0 ; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
delete [] pixels;
}

我们现在得到这些结果:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

从这里我们可以了解到std::vector可以与原始数组进行访问,但是如果你需要多次创建和删除vector/数组,在元素的构造函数没有内联的情况下,创建一个复杂的对象将比创建一个简单的数组花费更多的时间。我不认为这很令人惊讶。

试试这个:

void UseVectorCtor()
{
TestTimer t("UseConstructor");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
}
}

我得到了和数组几乎完全一样的性能。

关于vector的事情是,它是一个比数组更通用的工具。这意味着你在使用它时必须考虑如何。它可以以许多不同的方式使用,提供数组甚至没有的功能。如果您“错误”地使用了它,就会产生大量的开销,但如果您正确地使用它,它通常基本上是一个零开销的数据结构。在本例中,问题在于您分别初始化了vector(导致所有元素都调用了默认的ctor),然后用正确的值分别覆盖每个元素。对于编译器来说,这比对数组做同样的事情要困难得多。这就是为什么vector提供了一个构造函数,让你可以这样做:用值X初始化N元素。

当你使用它时,向量和数组一样快。

所以,你还没有打破性能神话。但是你已经证明了只有当你最优地使用向量时它才成立,这也是一个很好的观点。:)

好的一面是,它确实是简单的的使用是最快的。如果你对比我的代码片段(一行)和John Kugelman的答案,其中包含大量的调整和优化,这仍然没有完全消除性能差异,很明显vector是非常聪明的设计。你不必费尽周折才能得到等于数组的速度。相反,您必须使用最简单的解决方案。

通过正确的选项,向量和数组可以生成相同的asm。在这些情况下,它们的速度当然是一样的,因为无论哪种方式都可以得到相同的可执行文件。

当我第一次看您的代码时,这很难说是一个公平的比较;我还以为你不是在比较苹果和苹果。所以我想,让构造函数和析构函数在所有测试中都被调用;然后比较。

const size_t dimension = 1000;


void UseArray() {
TestTimer t("UseArray");
for(size_t j = 0; j < dimension; ++j) {
Pixel* pixels = new Pixel[dimension * dimension];
for(size_t i = 0 ; i < dimension * dimension; ++i) {
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = (unsigned char) (i % 255);
}
delete[] pixels;
}
}


void UseVector() {
TestTimer t("UseVector");
for(size_t j = 0; j < dimension; ++j) {
std::vector<Pixel> pixels(dimension * dimension);
for(size_t i = 0; i < dimension * dimension; ++i) {
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = (unsigned char) (i % 255);
}
}
}


int main() {
TestTimer t1("The whole thing");


UseArray();
UseVector();


return 0;
}

我的想法是,在这个设置下,它们应该是完全相同的。事实证明,我错了。

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

那么为什么会出现30%的性能损失呢?STL的所有内容都在头文件中,因此编译器应该能够理解所需的所有内容。

我的想法是,它是在循环如何初始化默认构造函数的所有值。所以我做了一个测试:

class Tester {
public:
static int count;
static int count2;
Tester() { count++; }
Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;


int main() {
std::vector<Tester> myvec(300);
printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);


return 0;
}

结果如我所料:

Default Constructed: 1
Copy Constructed: 300

这显然是减速的根源,因为vector使用复制构造函数从默认构造的对象初始化元素。

这意味着,以下伪操作顺序发生在向量的构造过程中:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

由于编译器创建了隐式复制构造函数,扩展为:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
vector[i].r = pixel.r;
vector[i].g = pixel.g;
vector[i].b = pixel.b;
}

因此默认的Pixel保持未初始化,而其余的正在初始化具有默认的Pixelun-initialised值。

New[]/Delete[]的替代情况相比:

int main() {
Tester* myvec = new Tester[300];


printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);


delete[] myvec;


return 0;
}


Default Constructed: 300
Copy Constructed: 0

它们都保留了未初始化的值,并且没有对序列进行两次迭代。

有了这些信息,我们如何进行测试呢?让我们试着重写隐式复制构造函数。

Pixel(const Pixel&) {}

结果呢?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

总之,如果你经常生成数百个向量:重新思考你的算法

在任何情况下,STL的实现都不会因为某些未知的原因变慢,它只是完全按照你的要求去做;希望你能明白。

我的笔记本电脑是联想G770 (4 GB内存)。

操作系统为Windows 7 64位(笔记本电脑版本)

编译器是MinGW 4.6.1。

IDE是块代码:

我测试了第一篇文章的源代码。

结果

O2优化

UseArray完成用时2.841秒

UseVector在2.548秒内完成

UseVectorPushBack在11.95秒内完成

全程用时17.342秒

系统暂停

O3优化

UseArray完成用时1.452秒

UseVector在2.514秒内完成

UseVectorPushBack在12.967秒内完成

全程用时16.937秒

在O3优化下,向量的性能更差。

如果你把循环改为

    pixels[i].r = i;
pixels[i].g = i;
pixels[i].b = i;

在O2和O3下,数组和矢量的速度几乎相同。

顺便说一下,你在使用vector的类中看到的减速也发生在标准类型中,比如int。这是一个多线程代码:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;


//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;


long long num=500000000;
int procs=1;


struct iterate
{
int id;
int num;
void * member;
iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};


//fill out viterate and piterate
void * viterate(void * input)
{
printf("am in viterate\n");
iterate * info=static_cast<iterate *> (input);
// reproduce member type
vector<int> test= *static_cast<vector<int>*> (info->member);
for (int i=info->id; i<test.size(); i+=info->num)
{
//printf("am in viterate loop\n");
test[i];
}
pthread_exit(NULL);
}


void * piterate(void * input)
{
printf("am in piterate\n");
iterate * info=static_cast<iterate *> (input);;
int * test=static_cast<int *> (info->member);
for (int i=info->id; i<num; i+=info->num) {
//printf("am in piterate loop\n");
test[i];
}
pthread_exit(NULL);
}


int main()
{
cout<<"producing vector of size "<<num<<endl;
vector<int> vtest(num);
cout<<"produced  a vector of size "<<vtest.size()<<endl;
pthread_t thread[procs];


iterate** it=new iterate*[procs];
int ans;
void *status;


cout<<"begining to thread through the vector\n";
for (int i=0; i<procs; i++) {
it[i]=new iterate(i, procs, (void *) &vtest);
//  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
}
for (int i=0; i<procs; i++) {
pthread_join(thread[i], &status);
}
cout<<"end of threading through the vector";
//reuse the iterate structures


cout<<"producing a pointer with size "<<num<<endl;
int * pint=new int[num];
cout<<"produced a pointer with size "<<num<<endl;


cout<<"begining to thread through the pointer\n";
for (int i=0; i<procs; i++) {
it[i]->member=&pint;
ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
}
for (int i=0; i<procs; i++) {
pthread_join(thread[i], &status);
}
cout<<"end of threading through the pointer\n";


//delete structure array for iterate
for (int i=0; i<procs; i++) {
delete it[i];
}
delete [] it;


//delete pointer
delete [] pint;


cout<<"end of the program"<<endl;
return 0;
}

代码中的行为表明vector的实例化是代码中最长的部分。一旦你通过瓶颈。其余的代码运行得非常快。无论在多少个线程上运行,这都是正确的。

顺便说一下,忽略那些疯狂的包含数。我一直在使用这段代码来测试一个项目的东西,所以包含的数量不断增长。

这是一个古老而流行的问题。

在这一点上,许多程序员将使用c++ 11。在c++ 11中,OP的代码在UseArrayUseVector中运行得同样快。

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

基本的问题是,当你的Pixel结构没有初始化时,std::vector<T>::resize( size_t, T const&=T() )接受一个默认构造的Pixel复制它。编译器没有注意到它被要求复制未初始化的数据,所以它实际执行了复制。

在c++ 11中,std::vector<T>::resize有两个重载。第一个是std::vector<T>::resize(size_t),另一个是std::vector<T>::resize(size_t, T const&)。这意味着当你不带第二个参数调用resize时,它只是默认构造,而编译器足够聪明,可以意识到默认构造什么也不做,所以它跳过了缓冲区的传递。

(添加这两个重载是为了处理可移动、可构造和不可复制类型——处理未初始化数据时的性能提升是一个额外的好处)。

push_back解决方案还执行围栏检查,这降低了它的速度,因此它仍然比malloc版本慢。

生活的例子(我也用chrono::high_resolution_clock替换了计时器)。

注意,如果你有一个通常需要初始化的结构,但你想在增加缓冲区后处理它,你可以使用自定义std::vector分配器来做到这一点。如果你想把它移动到一个更正常的std::vector,我相信谨慎使用allocator_traits和重写==可能会成功,但我不确定。

我只是想提一下vector(和smart_ptr)只是原始数组(和原始指针)上的一个薄层。 实际上在连续存储器中向量的访问时间比数组快。 下面的代码显示了初始化和访问vector和array的结果
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
srand (time(NULL));
vector<vector<int>> vector2d;
vector2d.reserve(SIZE);
int index(0);
boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
//  timer start - build + access
for (int i = 0; i < SIZE; i++) {
vector2d.push_back(vector<int>(SIZE));
}
boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
//  timer start - access
for (int i = 0; i < SIZE; i++) {
index = rand()%SIZE;
for (int j = 0; j < SIZE; j++) {


vector2d[index][index]++;
}
}
boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_duration msdiff = end - start_total;
cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
msdiff = end - start_acess;
cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n";




int index(0);
int** raw2d = nullptr;
raw2d = new int*[SIZE];
start_total = boost::posix_time::microsec_clock::local_time();
//  timer start - build + access
for (int i = 0; i < SIZE; i++) {
raw2d[i] = new int[SIZE];
}
start_access = boost::posix_time::microsec_clock::local_time();
//  timer start - access
for (int i = 0; i < SIZE; i++) {
index = rand()%SIZE;
for (int j = 0; j < SIZE; j++) {


raw2d[index][index]++;
}
}
end = boost::posix_time::microsec_clock::local_time();
msdiff = end - start_total;
cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
msdiff = end - start_acess;
cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n";
for (int i = 0; i < SIZE; i++) {
delete [] raw2d[i];
}
return 0;
}

输出结果为:

    Vector total time: 925milliseconds.
Vector access time: 4milliseconds.
Array total time: 30milliseconds.
Array access time: 21milliseconds.

所以如果你使用得当,速度几乎是一样的。 (正如其他人提到的使用reserve()或resize()).

一个更好的基准测试(我认为…),编译器由于优化可以改变代码,因为分配的向量/数组的结果不会在任何地方使用。 结果:< / p >
$ g++ test.cpp -o test -O3 -march=native
$ ./test
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

编译器:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

CPU:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

代码是:

#include <cstdlib>
#include <vector>


#include <iostream>
#include <string>


#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>


class TestTimer
{
public:
TestTimer(const std::string & name) : name(name),
start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
{
}


~TestTimer()
{
using namespace std;
using namespace boost;


posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
posix_time::time_duration d = now - start;


cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
" seconds" << endl;
}


private:
std::string name;
boost::posix_time::ptime start;
};


struct Pixel
{
Pixel()
{
}


Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
{
}


unsigned char r, g, b;
};


void UseVector(std::vector<std::vector<Pixel> >& results)
{
TestTimer t("UseVector inner");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel>& pixels = results.at(i);
pixels.resize(dimension * dimension);


for(int i = 0; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
}


void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
TestTimer t("UseVectorPushBack inner");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel>& pixels = results.at(i);
pixels.reserve(dimension * dimension);


for(int i = 0; i < dimension * dimension; ++i)
pixels.push_back(Pixel(255, 0, 0));
}
}


void UseArray(Pixel** results)
{
TestTimer t("UseArray inner");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);


results[i] = pixels;


for(int i = 0 ; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}


// free(pixels);
}
}


void UseArray()
{
TestTimer t("UseArray");
Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
UseArray(array);
for(int i=0;i<1000;++i)
free(array[i]);
free(array);
}


void UseVector()
{
TestTimer t("UseVector");
{
std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
UseVector(vector);
}
}


void UseVectorPushBack()
{
TestTimer t("UseVectorPush");
{
std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
UseVectorPushBack(vector);
}
}




int main()
{
TestTimer t1("The whole thing");


UseArray();
UseVector();
UseVectorPushBack();


return 0;
}

我不得不说我不是c++方面的专家。但要补充一些实验结果:

< p >编译: gcc-6.2.0 / bin / g + + o3化c++ 14 vector.cpp < / p >

机:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz

操作系统:

2.6.32-642.13.1.el6.x86_64

输出:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

这里我唯一感到奇怪的是“UseFillConstructor”的性能与“UseConstructor”相比。

代码:

void UseConstructor()
{
TestTimer t("UseConstructor");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel> pixels(dimension*dimension);
for(int i = 0; i < dimension * dimension; ++i)
{
pixels[i].r = 255;
pixels[i].g = 0;
pixels[i].b = 0;
}
}
}




void UseFillConstructor()
{
TestTimer t("UseFillConstructor");


for(int i = 0; i < 1000; ++i)
{
int dimension = 999;


std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
}
}

因此提供的额外“值”大大降低了性能,我认为这是由于多次调用复制构造函数造成的。但是…

编译:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

输出:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

因此,在这种情况下,gcc优化非常重要,但当一个值作为默认值提供时,它帮不了你太多。这,其实是对我的学费。希望它能帮助新程序员选择哪种矢量初始化格式。

我做了一些长期以来一直想做的广泛测试。不妨分享一下。

这是我的双启动机i7-3770, 16GB Ram, x86_64, Windows 8.1和Ubuntu 16.04。更多信息和结论,备注如下。测试了MSVS 2017和g++(在Windows和Linux上)。

测试程序

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>


// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];




int main()
{
std::locale loc("");
std::cout.imbue(loc);
constexpr int long size = 100000;  // largest array stack size


// stack allocated c array
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
int A[size];
for (int i = 0; i < size; i++)
A[i] = i;


auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";


// global stack c array
start = std::chrono::steady_clock::now();
for (int i = 0; i < g_size; i++)
g_A[i] = i;


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";


// raw c array heap array
start = std::chrono::steady_clock::now();
int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
for (int i = 0; i < size; i++)
AA[i] = i;


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
delete[] AA;


// std::array<>
start = std::chrono::steady_clock::now();
std::array<int, size> AAA;
for (int i = 0; i < size; i++)
AAA[i] = i;
//std::sort(AAA.begin(), AAA.end());


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";


// std::vector<>
start = std::chrono::steady_clock::now();
std::vector<int> v;
for (int i = 0; i < size; i++)
v.push_back(i);
//std::sort(v.begin(), v.end());


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";


// std::deque<>
start = std::chrono::steady_clock::now();
std::deque<int> dq;
for (int i = 0; i < size; i++)
dq.push_back(i);
//std::sort(dq.begin(), dq.end());


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";


// std::queue<>
start = std::chrono::steady_clock::now();
std::queue<int> q;
for (int i = 0; i < size; i++)
q.push(i);


duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

结果

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

笔记

  • 平均10次组装。
  • 我最初也使用std::sort()执行测试(你可以看到它被注释掉了),但后来删除了它们,因为没有显著的相对差异。

我的结论和评论

  • 请注意,全局c风格数组所花费的时间几乎与堆c风格数组相同
  • 在所有的测试中,我注意到std::array在连续运行之间的时间变化具有显著的稳定性,而其他测试,特别是std::数据结构的比较差异很大
  • O3优化未表现出明显的时间差
  • 删除Windows cl (no -O2)和g++ (Win/Linux no -O2, no -march=native)上的优化会显著增加时间。特别是对于std::数据结构。总体上MSVS上的时间比g++要高,但是std::array和c风格数组在没有优化的Windows上更快
  • g++生成的代码比微软的编译器更快(显然它甚至在Windows上运行得更快)。

判决

当然,这是用于优化构建的代码。既然问题是关于std::vector的,那么是的,它是!比普通数组(优化/未优化)慢。但是当您进行基准测试时,您自然希望生成优化的代码。

对我来说,这个节目的明星是std::array

这似乎取决于编译器标志。下面是一个基准代码:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>




int main(){


int size = 1000000; // reduce this number in case your program crashes
int L = 10;


std::cout << "size=" << size << " L=" << L << std::endl;
{
srand( time(0) );
double * data = new double[size];
double result = 0.;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
for( int l = 0; l < L; l++ ) {
for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
for( int i = 0; i < size; i++ ) result += data[i] * data[i];
}
std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Calculation result is " << sqrt(result) << "\n";
std::cout << "Duration of C style heap array:    " << duration << "ms\n";
delete data;
}


{
srand( 1 + time(0) );
double data[size]; // technically, non-compliant with C++ standard.
double result = 0.;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
for( int l = 0; l < L; l++ ) {
for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
for( int i = 0; i < size; i++ ) result += data[i] * data[i];
}
std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Calculation result is " << sqrt(result) << "\n";
std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
}


{
srand( 2 + time(0) );
std::vector<double> data( size );
double result = 0.;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
for( int l = 0; l < L; l++ ) {
for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
for( int i = 0; i < size; i++ ) result += data[i] * data[i];
}
std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Calculation result is " << sqrt(result) << "\n";
std::cout << "Duration of std::vector array:     " << duration << "ms\n";
}


return 0;
}

不同的优化标志给出不同的答案:

$ g++ -O0 benchmark.cpp
$ ./a.out
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp
$ ./a.out
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

您的确切结果会有所不同,但这在我的机器上是非常典型的。

根据我的经验,有时候,只是有时候,vector<int>可能比int[]慢很多倍。需要记住的一点是向量的向量与int[][]非常不同。因为元素在内存中可能不是连续的。这意味着你可以在主向量中调整不同向量的大小,但CPU可能无法像int[][]那样缓存元素。