是std :: vector比纯数组慢得多吗?


212

我一直认为这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 0减少UseVector一半(将其减少到4秒)。IMO,这确实是巨大的。


23
在调试模式下,某些版本的vector会添加额外的指令,以检查数组和类似内容的末尾是否可以访问。为了获得真实的时间,您必须以发布模式构建并打开优化。
马丁·约克

40
您进行了衡量,而不是相信您在互联网上听到的声明,这是很好的。
P

51
矢量实现为一个数组。那不是“常规智慧”,而是事实。您发现这vector是一个通用的可调整大小的数组。恭喜你 与所有通用工具一样,有可能会遇到次优的特殊情况。这就是为什么传统的智慧 a 开始vector并在必要时考虑替代方法的原因。
丹尼斯·齐克福斯

37
大声笑,“将脏盘子扔进水槽”和“将脏盘子扔进水槽并检查是否没有破损”的速度差异是多少?
伊姆雷(Imre L)2010年

9
至少在VC2010上,似乎主要的区别在于malloc()比resize()更快。从时序中删除内存分配,使用_ITERATOR_DEBUG_LEVEL == 0进行编译,结果是相同的。
Andreas Magnusson

Answers:


260

使用以下内容:

g ++ -O3 Time.cpp -I <MyBoost>
./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 <MyBoost>
./a.out
UseVector在2.216秒内完成

向量现在的性能仅比阵列稍差。IMO的这种差别微不足道,可能是由与测试无关的一堆东西引起的。

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


48
@ kizzx2:您需要使用reserve()而不是resize()。这为对象分配了空间(即,它更改了向量的容量),但没有创建对象(即,向量的大小保持不变)。
James McNellis 2010年

25
您正在执行1000000000000数组访问。时差为0.333秒。或每个阵列访问相差0.000000000333。假设像我的2.33 GHz处理器那样,每个阵列访问具有0.7个指令流水线级。因此,向量似乎每次访问都使用一条额外的指令。
马丁·约克2010年

3
@James McNellis:您不能只替换resize()reserve(),因为这不会调整向量自身大小的内部概念,因此,以后对其向量元素的写入在技术上都是“写到末尾”,并且会生成UB。尽管实际上每个STL实现都会在这方面“表现”,但是如何重新同步向量的大小?如果您resize() 填充向量尝试调用,则很有可能会使用默认构造的值覆盖所有这些元素!
j_random_hacker 2010年

8
@j_random_hacker:那不是我所说的吗?我以为我很清楚,reserve仅更改矢量的容量,而不更改其大小。
James McNellis

7
好,去吧 向量方法中有很多与异常相关的内容。添加/EHsc到编译开关清除了这一点,并且assign()实际上现在击败了数组。好极了。
帕维尔·米纳夫

55

好问题。我来到这里的目的是希望找到一些简单的解决方案,以加快向量测试的速度。那没有达到我预期的效果!

优化有帮助,但这还不够。通过优化,我仍然看到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%。仍然比数组慢。嗯

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

想法#4-使用迭代器代替循环索引

如何使用a 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

不,没什么不同。至少它并不慢。我以为它的性能类似于我使用Pixel&参考的#2 。

结论

即使某些智能cookie弄清楚了如何使向量循环与数组1一样快,这也并不能很好地说明的默认行为std::vector。如此之高的编译器足够聪明,可以优化所有C ++的性能,并使STL容器像原始数组一样快。

最重要的是,使用时,编译器无法优化no-op默认构造函数调用std::vector。如果使用普通格式,new[]则可以优化它们。但不是std::vector。即使您可以重写代码来消除面对此处的口头禅的构造函数调用:“编译器比您聪明。STL和普通C一样快。不必担心。”


2
再次感谢您实际运行代码。当有人尝试挑战流行观点时,有时候很容易毫无理由地遭到抨击。
kizzx2 2010年

3
“对于编译器来说,足够聪明以至于优化所有C ++并使STL容器像原始数组一样快。” 很好的评论。我有一个理论认为,这个“编译器很聪明”只是一个神话-C ++解析非常困难,而编译器只是一台机器。
kizzx2'9

3
我不知道。当然,他能够减慢阵列测试的速度,但是他没有加快向量测试的速度。我在上面进行了编辑,在这里我从Pixel删除了构造函数,并使其成为一个简单的结构,但它仍然很慢。对于使用像这样的简单类型的任何人来说,这都是一个坏消息vector<int>
约翰·库格曼

2
希望我真的能两次赞成你的回答。尝试甚至无法想到的精巧想法(即使没有一个可行)!
kizzx2'9

9
只是想说明一下,解析 C ++的复杂性(非常复杂,是的)与优化质量无关。后者通常发生在解析结果已经多次转换为低级表示的阶段。
帕维尔·米纳夫

44

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

此时,许多程序员都将使用C ++ 11。在C ++ 11中,所编写的OP代码对于UseArray或运行同样快UseVector

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和重写==可能会导致这种情况,但是不确定。


看看这里的emplace_backvs 怎么会很有趣push_back
丹尼尔(Daniel)

1
我无法复制您的结果。编译代码clang++ -std=c++11 -O3具有UseArray completed in 2.02e-07 secondsUseVector completed in 1.3026 seconds。我还添加了一个UseVectorEmplaceBack版本。2.5倍的速度UseVectorPushBack
丹尼尔(Daniel)

1
@daniel可能是优化程序从数组版本中删除了所有内容。微型基准始终存在风险。
Yakk-Adam Nevraumont

4
是的,您是对的,只是看了一下组装件(或没有组装件)。.鉴于〜6448514x的差异,应该考虑到这一点!我不知道为什么矢量版本无法进行同样的优化。如果使用尺寸而不是调整尺寸,则可以这样做。
丹尼尔(Daniel)

34

公平地说,您不能将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

但是稍作更改,表就会变成:

像素点

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

    unsigned char r, g, b;
};

像素

#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进行分配的复杂性太大,无法像simple那样有效地进行优化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可以与原始数组进行访问,但是如果您需要多次创建和删除vector / array,则创建复杂的对象将比创建简单的数组更耗时。当元素的构造函数未内联时。我认为这并不奇怪。


3
您仍然有一个内联构造函数-复制构造函数。
Ben Voigt

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),然后用正确的值分别覆盖了每个元素。与使用数组执行相同操作时相比,编译器最难以优化。这就是为什么向量提供了一个构造函数,让您可以精确地做到这一点的原因:NX

而且当您使用它时,向量与数组一样快。

所以,不,您没有破坏表演神话。但是您已经证明,只有最佳使用向量才是正确的,这也是一个很好的观点。:)

从好的方面来说,它实际上是最简单的用法,事实证明它是最快的。如果您将我的代码片段(单行)与John Kugelman的答案进行对比,其中包含大量的调整和优化,仍然不能消除性能差异,那么很显然,它vector毕竟设计得很巧妙。您不必为了获得与数组相等的速度而跳个圈。相反,您必须使用最简单的解决方案。


1
我仍然怀疑这是否是公平的比较。如果要摆脱内部循环,则等效的数组将是构造单个Pixel对象,然后将其遍历整个数组。
约翰·库格曼

1
使用与new[]执行相同的默认构造vector.resize(),但是速度要快得多。new[]+内部循环的速度应与vector.resize()+内部循环的速度相同,但不是,它的速度几乎快一倍。
约翰·库格曼

@约翰:这是一个公平的比较。在原来的代码中,数组分配与malloc不初始化或构建任何东西,所以它有效的一单通算法就像我的vector样品。至于new[]答案,显然两者都需要两次通过,但是在这种new[]情况下,编译器能够优化该额外开销,而在这种vector情况下则不会这样做。但是我不明白为什么在次优情况下发生的事情会很有趣。如果您关心性能,则不必编写这样的代码。
jalf

@John:有趣的评论。如果我想遍历整个数组,我想数组还是最好的解决方案-因为我不告诉vector::resize()我要给我一个连续的内存块,而不浪费时间调用无用的构造函数。
kizzx2'9

@ kizzx2:是和否。数组通常也在C ++中初始化。在C语言中,您将使用malloc不执行初始化的方法,但在非POD类型的C ++中将不起作用。因此,在一般情况下,C ++数组也是一样糟糕。也许问题是,如果您要经常执行此blitting,您是否会重用相同的数组/向量?如果这样做,那么从一开始就只需要支付一次“无用的构造函数”的费用。毕竟,实际的发声一样快。
jalf

22

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

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保持未初始化,而其余部分使用default 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的实现不会变慢,它恰好可以满足您的要求。希望你知道更多。


3
从我们(您和我以及其他聪明的人)所获得的乐趣来看,STL实现的“希望”确实是一个相当苛刻的要求:P基本上,我们可以夸张并得出结论,它希望我已经阅读并分析了所有源代码码。无论如何:P
kizzx2'9

1
好厉害!在VS 2013中,这使向量比数组更快。尽管对于性能至关重要的系统而言,您似乎需要大量测试STL才能有效使用它。
rozina

7

尝试禁用选中的迭代器并以发布模式构建。您应该不会看到太多的性能差异。


1
试过了#define _SECURE_SCL 0。这UseVector大约需要4秒钟的时间(与gcc下面类似),但速度却慢了一倍。
kizzx2'9

这几乎可以肯定是原因。Microsoft慷慨地默认为调试和发行版启用了我们的迭代器调试。我们发现,这是从2003年升级到2008年后大幅减速的根本原因。这绝对是Visual Studio最有害的陷阱之一。
Doug T.

2
@ kizzx2还有一个要禁用的宏:HAS_ITERATOR_DEBUGGING或类似的宏。
Doug T. 2010年

如@Martin和我的答案所示,即使使用进行了优化,gcc也会显示相同的模式-O3
约翰·库格曼

1
@Doug:查看该文档,我认为_HAS_ITERATOR_DEBUGGING它在发行版本中已被禁用:msdn.microsoft.com/en-us/library/aa985939
VS.80).aspx

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是未初始化垃圾的见解中发挥作用,并且无法优化循环和默认副本构造函数调用。您可以令人信服地认为,编译器绝对不应该对此进行优化,因为编写以上内容的程序员有一个合理的期望,即即使发生垃圾,所有对象在循环后都将是相同的(有关“ identical” / operator == vs的常见警告) memcmp / operator =等适用)。不能期望编译器对std :: vector <>的较大上下文有更多了解,或者以后对数据的使用将暗示这种优化是安全的。

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

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

我们可以期望编译器进行优化。

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

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

显然,如果我们使10000个独立对象与10000个引用同一数据的对象相比,这是一个主要区别。有一个合理的论点是,保护C ++临时用户免于意外地做一些昂贵的事情的好处超过了难以优化的复制构造的极小实际成本。

原始答复(供参考/理解注释):没有机会。向量与数组一样快,至少在您明智地保留空间的情况下。...


6
我真的不能证明这个答案对任何人都有用。我希望我可以两次投票。
kizzx2 2010年

-1,我对kizzx2表示支持。向量永远不会像数组那样快,这是因为它提供的附加功能,宇宙规则,一切都有代价!
YeenFei 2010年

Tony,您会错过了,它只是人为基准的一个示例,但确实可以证明它的目的。
Potatoswatter

玫瑰是绿色的,紫罗兰是橙色的,无花果的味道很苦,但是答案却很讨人喜欢。
帕维尔·米纳夫

3

马丁·约克(Martin York)的回答令我感到困扰,因为这似乎是在试图解决地毯下的初始化问题。但是他认为将冗余默认构造作为性能问题的根源是正确的。

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

对于眼前的问题,您当然可以调用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中的lambda使它变得更好了)。此外,我希望它仍然不会像malloc()基于非STL的版本那样快,但是我希望它会很接近,因为它仅对每个元素进行一次构造。


2

矢量对象还调用了Pixel构造函数。

每一次都会导致您要计时的几乎一百万个ctor运行。

编辑:然后有一个外部1 ... 1000循环,因此进行十亿个ctor调用!

编辑2:看到UseArray案例的反汇编很有趣。优化器可以优化整个过程,因为它除了燃烧CPU之外没有其他效果。


没错,但问题是:如何关闭这些毫无意义的ctor调用?对于非STL方法而言,这很容易,但是对于STL方法而言,则是困难/丑陋的。
j_random_hacker 2010年

1

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

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

调用push_backX项后:

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

重复。如果您没有reserving空间,它肯定会变慢。更重要的是,如果复制该商品的成本很高,那么像这样的“ push_back”将使您无法自拔。

至于vectorvs数组问题,我将不得不与其他人达成共识。在发行版中运行,打开优化功能,然后再添加一些标志,以便Microsoft友好的人员不会#@%$ ^成为现实。

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


我了解人们不喜欢一字不漏地阅读一堆代码。但是我确实reserve像我应该的那样使用。
kizzx2'9

对不起,我错过了。我在那里放的其他东西对您没有帮助吗?
小麦色的

push_back已摊销固定时间。听起来您正在描述O(N)进程。(步骤1和3似乎完全不合时宜。)使push_backOP变慢的是范围检查,以查看是否需要进行重新分配,更新指针,对放置位置中的NULL进行检查new以及其他通常被淹没的小事情该程序的实际工作。
Potatoswatter

即使reserve它仍然必须在每个上进行该检查(是否需要重新分配),它也会变慢push_back
帕维尔·米纳夫

所有的优点。我正在描述的内容听起来像是O(N)流程,但事实并非如此。我认识的大多数人都不了解a vector的功能如何执行其调整大小功能,这只是“魔术”。在这里,让我再澄清一下。
小麦色

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

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


2
不幸的是,您提供的“解决方案”之后pixels.size()将被废除。
kizzx2 2011年

1
这是错误的,您不能调用reserve然后使用元素,您仍然必须使用push_back添加项目
paulm 2014年

1

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

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

编译器为MinGW 4.6.1。

IDE是Code :: Blocks

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

结果

氧气优化

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


即使我将malloc更改为new,在O3下的第一个测试用例中,vector的性能仍然比array慢。但是当您将赋值从(255,0,0)更改为(i,i,i)时,向量和数组在O2和O3下几乎相同,这很奇怪
StereoMatching 2012年

对不起,我忘了自由删除。自由删除后,向量和数组在O3下的性能现在是相同的,看起来分配器是主要原因吗?
StereoMatching 2012年

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)

中央处理器:

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

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(no -O2)和g ++(Win / Linux no -O2,no -march = native)上删除优化会大大增加时间。特别是对于std :: data结构。总的来说,MSVS上的时间要比g ++更长,但是std::array在Windows上无需优化就可以更快地在Windows上使用c样式的数组
  • g ++生成的代码比Microsoft的编译器更快(显然,即使在Windows上,它也可以运行得更快)。

判决

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

对我来说,演出的明星一直是std::array


0

使用正确的选项,向量和数组可以生成相同的asm。在这些情况下,它们的速度当然是相同的,因为您会以两种方式获得相同的可执行文件。


1
在这种情况下,它们似乎不会生成相同的程序集。特别是,似乎没有办法抑制使用向量对构造函数的调用。您可以在此处参考该问题的答案(虽然已读了很长时间,但确实可以解释为什么您所提供的链接中的简单测试用例以外的情况在性能上会有差异。)(实际上,似乎有一种方法- -根据建议编写自定义STL分配器。就我个人而言,我发现这比使用malloc不必要地多了)
kizzx2 2010年

1
@ kizzx2:您必须竭尽全力才能使用构造对象是一件好事,因为这是99%的错误(我可能会严重低估)。我确实阅读了其他答案,但我意识到我没有针对您的具体情况(不需要,其他答案都是正确的),但是我仍然想向您提供此示例,说明向量和数组的行为完全相同。

@Roger:太好了!感谢您的链接
kizzx2 2010年

0

顺便说一句,使用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;
}

代码的行为表明向量的实例化是代码的最长部分。一旦穿过瓶颈。其余代码运行非常快。不管您正在运行多少个线程,这都是事实。

顺便说一下,忽略包含的绝对疯狂数目。我一直在使用此代码来测试项目的事物,因此包含的数目不断增长。


0

我只想提到向量(和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())。


0

好吧,因为vector :: resize()比普通的内存分配(通过malloc)进行的处理要多得多。

尝试在您的副本构造函数中放置一个断点(定义断点以便可以断点!),这会增加额外的处理时间。


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

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

代码:

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


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

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


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.