Is std::vector so much slower than plain arrays?
我一直认为
以下是一些测试结果:
1 2 3 4 | 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倍!对于"
我使用的代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #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中使用发布模式。
在VisualC++中,EDCOX1(2)减少了EDOCX1×3的一半(将其降低到4秒)。这真是太大了,我的意思是。
使用以下方法:
g++ -O3 Time.cpp -I
./a.out
UseArray completed in 2.196 seconds
UseVector completed in 4.412 seconds
UseVectorPushBack completed in 8.017 seconds
The whole thing completed in 14.626 seconds
所以数组的速度是向量的两倍。
但在更详细地研究了代码之后,这是意料之中的;因为您在向量上运行了两次,而数组只运行了一次。注意:当您使用cx1〔0〕这个向量时,您不仅要分配内存,而且还要运行该向量并调用每个成员的构造函数。
重新排列代码,使向量只初始化每个对象一次:
1 | std::vector<Pixel> pixels(dimensions * dimensions, Pixel(255,0,0)); |
现在再次进行相同的计时:
g++ -O3 Time.cpp -I
./a.out
UseVector completed in 2.216 seconds
矢量现在的性能只比数组稍差一点。在我看来,这种差异是微不足道的,可能是由一大堆与测试无关的事情造成的。
我还考虑到您没有正确地初始化/销毁
好问题。我来到这里,希望能找到一些简单的修复方法来加速向量测试。那不是我想象的那样!
优化有帮助,但还不够。在优化方面,我仍然看到usearray和usevector之间的性能差异是2倍。有趣的是,在没有优化的情况下,usevector比usevectorbushback慢得多。
1 2 3 4 5 6 7 8 9 10 11 12 | # 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-使用新的[]而不是malloc
我尝试在usearray中将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } } |
令人惊讶的是(对我来说),这些变化都没有什么不同。甚至没有对
我还试图摆脱三重EDOCX1查询(6),并缓存对EDOCX1的引用(7)。这实际上减慢了usevector的速度!哎呀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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可以在创建向量时优化所有对象的构造。如果我们将像素更改为:
1 2 3 4 | struct Pixel { unsigned char r, g, b; }; |
结果:大约快10%。仍然比数组慢。嗯。
1 2 3 | # ./vector UseArray completed in 3.239 seconds UseVector completed in 5.567 seconds |
IDEA 4-使用迭代器而不是循环索引
用
1 2 3 4 5 6 | for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j) { j->r = 255; j->g = 0; j->b = 0; } |
结果:
1 2 3 | # ./vector UseArray completed in 3.264 seconds UseVector completed in 5.443 seconds |
不,没什么不同。至少速度不慢。我认为这会有类似于我使用
即使一些智能cookie知道如何使向量循环与数组循环一样快,这也不能很好地说明
底线是编译器在使用
这是一个古老但流行的问题。
在这一点上,许多程序员将在C++ 11中工作。在C++ 11中,作为写入的OP代码,对于EDCOX1、0、EDCOX1、1,运行速度同样快。
1 2 | UseVector completed in 3.74482 seconds UseArray completed in 3.70414 seconds |
根本问题是,当您的
在C++ 11中,EDCOX1(5)有两个重载。第一个是
(添加两个重载来处理可移动、可构造和不可复制的类型——在处理未初始化的数据时提高性能是一个额外的好处)。
现场示例(我还用
请注意,如果您有一个通常需要初始化的结构,但您希望在增大缓冲区后处理它,那么可以使用自定义的
公平地说,你不能将C++实现与C实现相比较,就像我将调用你的MALLC版本一样。malloc不创建对象-它只分配原始内存。然后你把内存当作对象,而不调用构造函数是很差的C++(可能是无效的——我会把它留给语言的律师)。
也就是说,简单地将malloc更改为
1 2 3 4 | 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 |
但只要稍微改变一下,桌子就会转动:
像素H1 2 3 4 5 6 7 | struct Pixel { Pixel(); Pixel(unsigned char r, unsigned char g, unsigned char b); unsigned char r, g, b; }; |
象素
1 2 3 4 5 | #include"Pixel.h" Pixel::Pixel() {} Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b) {} |
MIN
1 2 3 | #include"Pixel.h" [rest of test harness without class Pixel] [UseArray now uses new/delete not malloc/free] |
以这种方式编译:
1 2 3 | $ g++ -O3 -c -o Pixel.o Pixel.cc $ g++ -O3 -c -o main.o main.cc $ g++ -o main main.o Pixel.o |
我们得到了非常不同的结果:
1 2 3 4 | 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 |
对于像素的非内联构造函数,std::vector现在胜过原始数组。
通过std::vector和std:allocator进行分配的复杂性似乎过于复杂,无法像简单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; } } } |
和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; } |
我们现在得到这些结果:
1 2 3 4 | 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与用于访问的原始数组类似,但是如果您需要多次创建和删除向量/数组,那么在元素的构造函数没有内联的情况下,创建复杂对象将比创建简单数组花费更多的时间。我认为这并不令人惊讶。
尝试一下:
1 2 3 4 5 6 7 8 9 10 11 | 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)); } } |
我得到的性能与使用阵列几乎完全相同。
关于
当你使用它的时候,向量和数组一样快。
所以不,你没有打破表演神话。但是你已经证明了只有在你优化使用向量的情况下才是正确的,这也是一个很好的观点。:)
从好的方面来说,这确实是最简单的用法,结果却是最快的。如果您将我的代码片段(一行)与约翰·库格曼的答案进行对比,其中包含大量的调整和优化,但仍然不能完全消除性能差异,那么很明显,
当我第一次看你的代码时,这是一个不公平的比较;我肯定认为你不是在比较苹果和苹果。所以我想,让我们在所有测试中调用构造函数和析构函数,然后进行比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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; } |
我的想法是,有了这个设置,它们应该是完全相同的。结果,我错了。
1 2 3 | UseArray completed in 3.06 seconds UseVector completed in 4.087 seconds The whole thing completed in 10.14 seconds |
那么,为什么会出现这种30%的性能损失呢?STL头中包含所有内容,因此编译器应该可以理解所需的所有内容。
我的想法是循环是如何将所有值初始化为默认构造函数的。所以我做了一个测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 Copy Constructed: %i ", Tester::count, Tester::count2); return 0; } |
我怀疑结果是:
1 2 | Default Constructed: 1 Copy Constructed: 300 |
这显然是减速的原因,即向量使用复制构造函数从默认构造的对象初始化元素。
这意味着,在构造向量的过程中会发生以下伪操作顺序:
1 2 | Pixel pixel; for (auto i = 0; i < N; ++i) vector[i] = pixel; |
由于编译器所做的隐式复制构造函数,将其扩展为以下内容:
1 2 3 4 5 6 | Pixel pixel; for (auto i = 0; i < N; ++i) { vector[i].r = pixel.r; vector[i].g = pixel.g; vector[i].b = pixel.b; } |
因此,默认的
与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int main() { Tester* myvec = new Tester[300]; printf("Default Constructed: %i Copy Constructed:%i ", Tester::count, Tester::count2); delete[] myvec; return 0; } Default Constructed: 300 Copy Constructed: 0 |
它们都留给未初始化的值,并且没有对序列进行双重迭代。
有了这些信息,我们如何测试它?让我们尝试重写隐式复制构造函数。
1 | Pixel(const Pixel&) {} |
结果呢?
1 2 3 | UseArray completed in 2.617 seconds UseVector completed in 2.682 seconds The whole thing completed in 5.301 seconds |
总之,如果你经常做数百个向量:重新考虑你的算法。
在任何情况下,STL的实现都不会因为某些未知的原因而变慢,它只是按照您的要求执行;希望您能更好地了解。
不尝试disabling iterators和建筑在释放模式。你不应该看到多的性能差。
GNU的STL(和其他),并
是这样的:
1 | vector<T> x(n); |
或
1 2 | vector<T> x; x.resize(n); |
是很多东西:STL的实现类
1 2 3 | T temp; for (int i = 0; i < n; ++i) x[i] = temp; |
我的问题是,当前一代的编译器优化器不似乎是在工作温度uninitialised Insight是垃圾,和一个外环和优化的故障invocations默认复制构造函数。你可以认为是credibly编译器不能优化本了,作为一个程序员写上面有一合理的期望,所有的对象是相同的后环,即使垃圾(通常的警告对"相同的"memcmp /操作员/ VS = = =运算符等应用)。一个编译器可以预期有任何额外的洞察到他们的所有,性病::向量>或以后使用的数据,我们建议本优化安全。
这可以与更多的理解上,直接实现:
1 2 | for (int i = 0; i < n; ++i) x[i] = T(); |
但我们可以享受到了编译器的优化。
一个显示一位更多更好的这方面的考虑:向量的行为,
1 | std::vector<big_reference_counted_object> x(10000); |
我们是一个专业化的独立差分IF在10000和10000引用相同的数据对象。有一个合理的说法是,领先的休闲用户和保护您的C + +做的东西如此昂贵的outweights真实世界非常小的硬件成本的建设到优化复制。
原始的答案(参考/制作:意义上的评论)NO的机会。他几乎是作为一个矢量阵列,至少如果你sensibly储备空间。……
马丁·约克的回答让我很不安,因为它似乎是在地毯下刷初始化问题。但他认为冗余缺省构造是性能问题的根源是正确的。
[编辑:Martin的回答不再建议更改默认构造函数。]
对于眼前的问题,您当然可以调用
1 | std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0)); |
如果您想用一个常量值初始化,这是很常见的情况。但更普遍的问题是:如何有效地用比常量更复杂的值初始化?
为此,您可以使用一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #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()); |
或者,您可以使用
缺点是构造初始值的逻辑需要移动到一个单独的类中,这比将其放在适当的位置更不方便(尽管C++ 1x中的lambdas使这更好)。此外,我预计这仍然不会像基于
矢量的一个额外调用了像素构造器。
每一个都会导致你正在计时的将近一百万个ctor运行。
编辑:那么外面有1…1000个循环,所以让10亿个ctor调用!
编辑2:看到usearray案例的反汇编会很有趣。优化器可以对整个过程进行优化,因为它除了烧掉CPU之外没有任何效果。
我的笔记本电脑是Lenova G770(4 GB RAM)。
操作系统是Windows 7 64位(带笔记本电脑的操作系统)
编译器是mingw 4.6.1。
IDE是代码::块。
我测试了第一个帖子的源代码。
结果O2优化
usearray在2.841秒内完成
usevector在2.548秒内完成
usevectorbushback在11.95秒内完成
整个过程在17.342秒内完成
系统暂停
O3优化
usearray在1.452秒内完成
usevector在2.514秒内完成
使用VectorShushback在12.967秒内完成
整个过程在16.937秒内完成
在O3优化下,向量的性能似乎更差。
如果将循环更改为
1 2 3 | pixels[i].r = i; pixels[i].g = i; pixels[i].b = i; |
阵列和矢量在O2和O3下的速度几乎相同。
一些探查器数据(像素与32位对齐):
1 2 3 4 5 | 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 |
瞎说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 */ |
在
1 2 3 4 5 | : // _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); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | :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; : : } |
数组
1 2 3 4 5 6 7 8 9 10 11 12 | :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; |
大部分开销都在复制构造函数中。例如,
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
它与数组具有相同的性能。
一个更好的基准(我认为…),编译器由于优化可以改变代码,因为分配向量/数组的结果不在任何地方使用。结果:
1 2 3 4 5 6 7 8 9 | $ 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 |
编译程序:
1 | gcc version 6.2.0 20161019 (Debian 6.2.0-9) |
CPU:
1 | model name : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz |
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #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; } |
这里是如何工作的:在
售后服务电话:
重复序列。如果你需要空间去
作为一
一个更多的东西,如果你不需要调整,boost.array使用。
顺便说一下,在使用vector的类中,使用int等标准类型也会减慢速度。这里是一个多线程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | #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 "); 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 "); test[i]; } pthread_exit(NULL); } void * piterate(void * input) { printf("am in piterate "); 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 "); 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 "; 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 "; 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 "; //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; } |
代码中的行为表明向量的实例化是代码中最长的部分。一旦你通过瓶颈。其余的代码运行得非常快。不管运行多少线程,这都是正确的。
顺便说一下,忽略包含的绝对疯狂的数量。我一直在使用这段代码来测试一个项目的内容,因此包含的内容不断增加。
使用正确的选项,向量和数组可以生成相同的ASM。在这些情况下,它们的速度当然是相同的,因为无论哪种方式,都可以获得相同的可执行文件。
我做了一些我想做一段时间的广泛测试。不妨分享一下。
这是我的双引导机器i7-3770,16GB RAM,x86_64,在Windows 8.1和Ubuntu 16.04上。更多信息和结论,备注如下。测试了MSVs2017和G++(在Windows和Linux上)。
测试程序1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #include <iostream> #include <chrono> //#include #include #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 "; std::cout <<"c-style stack array size=" << sizeof(A) <<"B "; // 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 "; std::cout <<"global c-style stack array size=" << sizeof(g_A) <<"B "; // 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 "; std::cout <<"c-style heap array size=" << sizeof(AA) <<"B "; 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 "; std::cout <<"std::array size=" << sizeof(AAA) <<"B "; // 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 "; std::cout <<"std::vector size=" << v.size() * sizeof(v.back()) <<"B "; // 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 "; std::cout <<"std::deque size=" << dq.size() * sizeof(dq.back()) <<"B "; // 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 "; std::cout <<"std::queue size=" << q.size() * sizeof(q.front()) <<"B "; } |
结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | ////////////////////////////////////////////////////////////////////////////////////////// // 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::data结构在比较中变化很大。 - O3优化没有显示任何值得注意的时间差异
- 删除Windows cl(no-o2)和g++上的优化(win/linux no-o2,no-march=native)会显著增加时间。特别是对于std::data结构。MSV上的总时间比G++高,但在Windows上,
std::array 和C样式的阵列更快,而无需优化 - G++生成的代码比微软的编译器更快(显然,它甚至在Windows上也运行得更快)。
判决
当然,这是用于优化构建的代码。既然问题是关于
不过,对我来说,这场演出的明星是江户十一〔一〕号。
我只想说向量(和智能指针)只是原始数组(和原始指针)上的一个薄层附加。实际上,向量在连续内存中的访问时间比数组快。下面的代码显示了初始化和访问向量和数组的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #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. "; msdiff = end - start_acess; cout <<"Vector access time:" << msdiff.total_milliseconds() <<"milliseconds. "; 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. "; msdiff = end - start_acess; cout <<"Array access time:" << msdiff.total_milliseconds() <<"milliseconds. "; for (int i = 0; i < SIZE; i++) { delete [] raw2d[i]; } return 0; } |
输出是:
1 2 3 4 | Vector total time: 925milliseconds. Vector access time: 4milliseconds. Array total time: 30milliseconds. Array access time: 21milliseconds. |
所以如果使用得当,速度几乎是一样的。(正如其他人提到的使用reserve()或resize())。
好吧,因为vector::resize()比普通内存分配(malloc)做的更多。
尝试在复制构造函数中放置断点(定义它以便可以断点!)还有额外的处理时间。
我得说我不是C++专家。但要添加一些实验结果:
编译:GCC-62.0/BI/G++-O3-STD= C++ 14矢量CPP
机器:
1 | Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz |
操作系统:
1 | 2.6.32-642.13.1.el6.x86_64 |
输出:
1 2 3 4 5 6 | 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"相比。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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)); } } |
所以提供的额外"值"会大大降低性能,我认为这是由于多次调用复制构造函数造成的。但是…
编译:
1 | gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp |
输出:
1 2 3 4 5 6 | 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优化是非常重要的,但是当一个值作为默认值提供时,它并不能帮助您很多。这实际上是我的学费。希望它能帮助新程序员选择哪种向量初始化格式。