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

时间:2010-09-08 02:38:42

标签: c++ arrays performance stl 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中使用发布模式。


Visual C++中,#define _SECURE_SCL 0UseVector减少一半(将其降低到4秒)。这真是巨大的,IMO。

22 个答案:

答案 0 :(得分:251)

使用以下内容:

  

g ++ -O3 Time.cpp -I&lt; MyBoost&gt;
  ./a.out
  UseArray在2.196秒完成   UseVector在4.412秒完成
  UseVectorPushBack在8.017秒完成
  整件事在14.626秒内完成

因此数组的速度是矢量的两倍。

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

稍微重新排列代码,以便向量仅初始化每个对象一次:

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

现在再次做同样的时间:

  

g ++ -O3 Time.cpp -I&lt; MyBoost&gt;
  ./a.out
  UseVector在2.216秒内完成

现在的矢量性能仅略差于数组。 IMO这种差异是微不足道的,可能是由一大堆与测试无关的事情引起的。

我还会考虑到你没有正确地初始化/销毁UseArrray()方法中的Pixel对象,因为没有调用构造函数/析构函数(这可能不是这个简单类的问题,但是更复杂(即使用指针或带指针的成员)会导致问题。

答案 1 :(得分:52)

好问题。我来到这里期待找到一些简单的修复,可以加快矢量测试的速度。这没有像我预期的那样有效!

优化有所帮助,但还不够。通过优化,我仍然看到UseArray和UseVector之间的性能差异为2倍。有趣的是,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 []调用

我还试图摆脱三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可以在创建向量时优化所有对象的构造。如果我们将Pixel更改为:

会发生什么
struct Pixel
{
    unsigned char r, g, b;
};

结果:大约快10%。仍然比阵列慢。 HM。

# ./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计算出如何使向量循环与数组一样快,但这并不能说明std::vector的默认行为。因为编译器非常聪明,可以优化所有C ++,并使STL容器与原始数组一样快。

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

答案 2 :(得分:36)

这是一个古老而又受欢迎的问题。

此时,许多程序员将使用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解决方案还会对fencepost进行检查,从而减慢速度,因此它仍然比malloc版本慢。

live example(我还用chrono::high_resolution_clock替换了计时器。)

请注意,如果您的结构通常需要初始化,但是您希望在增长缓冲区后处理它,则可以使用自定义std::vector分配器来执行此操作。如果您想将其移至更正常的std::vector,我相信谨慎使用allocator_traits并覆盖==可能会将其拉下来,但我不确定。

答案 3 :(得分:33)

公平地说,您无法将C ++实现与C实现进行比较,因为我将其称为malloc版本。 malloc不会创建对象 - 它只分配原始内存。然后你将内存视为对象而不调用构造函数是可怜的C ++(可能无效 - 我会把它留给语言律师)。

也就是说,只需将malloc更改为new Pixel[dimensions*dimensions]并将其自由更改为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]那样有效地进行优化。但是,我们可以看到问题只是分配而不是矢量访问,通过调整一些测试函数来创建矢量/数组一次,将其移到循环外:

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与用于访问的原始数组相当,但是如果你需要多次创建和删除矢量/数组,那么创建一个复杂的对象会比创建更复杂的对象更耗时元素的构造函数未内联时的简单数组。我不认为这是非常令人惊讶的。

答案 4 :(得分:26)

试试这个:

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的事情是,它是一个比数组更通用的工具。这意味着您必须考虑如何使用它。它可以以多种不同的方式使用,提供阵列甚至没有的功能。如果你为了你的目的使用它“错误”,你会产生很多开销,但如果你正确使用它,它通常基本上是一个零开销的数据结构。在这种情况下,问题是您单独初始化向量(导致所有元素都调用其默认ctor),然后使用正确的值单独覆盖每个元素。与使用数组执行相同操作相比,编译器更难以优化。这就是为什么向量提供了一个构造函数,它可以让你做到这一点:初始化值为N的{​​{1}}个元素。

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

所以不,你没有破坏性能神话。但是你已经证明,只有你最佳地使用矢量才是真的,这也是一个非常好的观点。 :)

从好的方面来看,实际上最简单的用法最快。如果你将我的代码片段(一行)与John Kugelman的答案进行对比,包含大量的调整和优化,但仍然没有完全消除性能差异,很明显X在设计之后非常巧妙地设计了所有。你不必跳过箍来获得等于阵列的速度。相反,您必须使用最简单的解决方案。

答案 5 :(得分:21)

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

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

这显然是减速的根源,即向量使用复制构造函数初始化默认构造对象中的元素。

这意味着,在构造向量期间发生了以下伪操作顺序:

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仍然未初始化,其余已初始化,默认Pixel未初始化值。

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 实现并不慢,它只是按照你的要求行事;希望你能更清楚。

答案 6 :(得分:7)

尝试禁用checked iterators并在发布模式下构建。你不应该看到很多性能差异。

答案 7 :(得分:4)

GNU的STL(和其他人),给定vector<T>(n),默认构造一个原型对象T() - 编译器将优化掉空构造函数 - 但随后会在内存中发生任何垃圾的副本现在为该对象保留的地址由STL的__uninitialized_fill_n_aux获取,该循环将该对象的副本填充为向量中的默认值。所以,“我的”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 apply)。不能期望编译器对std :: vector&lt;&gt;的更大上下文有任何额外的了解。或者稍后使用表明这种优化安全的数据。

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

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

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

为了更明确地说明向量行为的这一方面的理由,请考虑:

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

显然,如果我们制作10000个独立对象而不是10000个引用相同数据,那么这是一个主要区别。有一个合理的论点是,保护临时C ++用户不会意外地做一些如此昂贵的事情的优势远远超过了难以优化的复制结构的实际成本。

原始答案(供参考/理解评论): 没有机会。矢量和阵列一样快,至少如果你明智地保留空间。 ...

答案 8 :(得分:3)

Martin York's answer困扰我,因为它似乎试图在地毯下刷新初始化问题。但他认为冗余的默认结构是性能问题的根源是正确的。

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

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

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

如果你想用常量值初始化,这是常用的。但更普遍的问题是:如何使用比常量值更复杂的东西来有效地初始化?

为此,您可以使用back_insert_iterator,它是一个迭代器适配器。这是一个带有int s向量的示例,尽管一般的想法对Pixel s也有效:

#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使得这个更好)。此外,我预计这仍然不会像基于malloc()的非STL版本一样快,但我希望它会接近,因为它只为每个元素做一个构造。

答案 9 :(得分:2)

向量的另外还调用Pixel构造函数。

每个人都会造成近百万次ctor,你正在计时。

编辑:然后是外部的1 ... 1000循环,所以让这个十亿ctor调用!

编辑2:看到UseArray案例的反汇编很有意思。优化器可以优化整个过程,因为除了刻录CPU之外它没有任何影响。

答案 10 :(得分:1)

一些分析器数据(像素对齐到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;
    }

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

答案 11 :(得分:1)

以下是向量中push_back方法的工作原理:

  1. 向量在初始化时分配X空间量。
  2. 如下所述,它会检查项目的当前基础数组中是否有空间。
  3. 它会在push_back调用中复制该项目。
  4. 调用push_back X项后:

    1. 向量将kX空间量重新分配到第二个数组中。
    2. 它将第一个数组的条目复制到第二个数组。
    3. 丢弃第一个数组。
    4. 现在使用第二个数组作为存储,直到达到kX条目。
    5. 重复。如果你不是reserving空间,它肯定会变慢。更重要的是,如果复制项目的成本很高,那么'push_back'会让你活着。

      关于vector与阵列的关系,我将不得不与其他人达成一致。在发布版中运行,打开优化,并添加一些标记,以便Microsoft的友好人员不会#@%$ ^它为你起作用。

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

答案 12 :(得分:1)

我的笔记本电脑是Lenova G770(4 GB RAM)。

操作系统是Windows 7 64位(带笔记本电脑的)

编译器是MinGW 4.6.1。

IDE是Code::Blocks

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

结果

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下的数组和矢量速度几乎相同。

答案 13 :(得分:1)

我做了一些我想做的广泛测试。最好也分享一下。

这是Windows 8.1和Ubuntu 16.04上的双启动计算机i7-3770、16GB Ram,x86_64。更多信息和结论,在下面的评论。测试了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(无-O2)和g ++(Win / Linux无-O2,无-march = native)上取消优化会大大增加时间。特别是对于std :: data结构。总的来说,MSVS的时间要比g ++高,但std::array和c样式的数组在Windows上无需优化即可更快
  • g ++生成的代码比Microsoft的编译器更快(显然,即使在Windows上,它也可以运行得更快)。

判决

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

对我来说,节目的主角是std::array

答案 14 :(得分:1)

一个更好的基准测试(我认为......),由于优化的编译器可以改变代码,因为分配的矢量/数组的结果不会在任何地方使用。 结果:

$ 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;
}

答案 15 :(得分:0)

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

#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

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

答案 16 :(得分:0)

根据我的经验,有时vector<int>可能比int[]慢许多倍。要记住的一件事是向量的向量与int[][]非常不同。由于元素可能在内存中不连续。这意味着您可以在主向量中调整不同向量的大小,但是CPU可能无法像int[][]一样缓存元素。

答案 17 :(得分:0)

我不得不说我不是C ++方面的专家。但要添加一些实验结果:

编译:     gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

机器:

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

这里我唯一感到奇怪的是&#34; UseFillConstructor&#34;性能与&#34; UseConstructor&#34;。

相比

代码:

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));
    }
}

所以额外的&#34;值&#34;提供的性能降低了很多,我认为是由于多次调用复制构造函数。但...

编译:

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优化非常重要,但是当默认提供值时,它可以帮助您。这实际上违背了我的学费。希望它能帮助新程序员选择哪种向量初始化格式。

答案 18 :(得分:0)

我只想提一下,vector(和smart_ptr)只是在原始数组(和原始指针)之上添加一个薄层。 实际上,矢量在连续存储器中的访问时间比数组快。 以下代码显示了初始化和访问向量和数组的结果。

#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())。

答案 19 :(得分:0)

顺便说一下,使用向量减慢你在类中看到的速度也会出现在像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的实例化是代码中最长的部分。一旦你通过那个瓶颈。其余的代码运行速度非常快。无论您运行多少个线程,都是如此。

顺便说一句,忽略绝对疯狂的包含数量。我一直在使用这段代码来测试项目的内容,因此包含的数量在不断增长。

答案 20 :(得分:0)

使用正确的选项,矢量和数组可以generate identical asm。在这些情况下,它们当然具有相同的速度,因为无论哪种方式都可以获得相同的可执行文件。

答案 21 :(得分:0)

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

尝试在复制构造函数中放置一个断点(定义它以便断点!)并且需要额外的处理时间。