C ++ std :: vector比C99可变长度数组慢吗?

时间:2014-10-23 19:57:58

标签: c++

我已经看过一些关于此事的讨论。许多人说他们应该是同样的速度。 但我自己做了一些测试。在我看来,使用std :: 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;
};


using namespace std;

int main(int argc, char** argv)
{
  // timing for vector calculations
  {
    int n = 100000;
    std::vector<double> a(n),b(n),c(n);

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    TestTimer t("vector");
    for ( long int j = 0; j < 1000; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];
  }

  // timing for array calculations
  {
    int n = 100000;
    double a[n],b[n],c[n];

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    TestTimer t("array");
    for ( long int j = 0; j < 1000; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];
  }

}

我使用-O0或-O3编译并运行icpc代码(g ++给出了非常相似的结果。我重复了几次,结果是一样的。):

icpc test.C -o test.x -O3 -Fa -g
./test.x
vector completed in 0.06 seconds
array completed in 0.03 seconds


icpc test.C -o test.x -O0 -Fa -g
./test.x
vector completed in 0.269 seconds
array completed in 0.279 seconds

看起来像-O3,使用数组计算比使用向量快2倍。我查看了汇编代码(对于循环中的部分)。对于使用-O0编译的汇编代码,它们对于矢量和数组看起来相同。但是当用-O3编译时,它们看起来完全不同。 (见下文) 我有点理解代码试图将数据从内存移动到寄存器(使用movapsx),执行乘法(mulpdx)并将数据移回(moapsx)。但是movsdq,movhpdq和movapsx有什么不同?为什么编译器会为数组和向量生成不同的代码?

汇编代码(循环部分)与-O3使用向量:

movsdq  (%rbx,%r13,8), %xmm0
movsdq  0x10(%rbx,%r13,8), %xmm1
movsdq  0x20(%rbx,%r13,8), %xmm2
movsdq  0x30(%rbx,%r13,8), %xmm3
movhpdq  0x8(%rbx,%r13,8), %xmm0
movhpdq  0x18(%rbx,%r13,8), %xmm1
movhpdq  0x28(%rbx,%r13,8), %xmm2
movhpdq  0x38(%rbx,%r13,8), %xmm3
mulpdx  (%r9,%r13,8), %xmm0
mulpdx  0x10(%r9,%r13,8), %xmm1
mulpdx  0x20(%r9,%r13,8), %xmm2
mulpdx  0x30(%r9,%r13,8), %xmm3
movapsx  %xmm0, (%r15,%r13,8)
movapsx  %xmm1, 0x10(%r15,%r13,8)
movapsx  %xmm2, 0x20(%r15,%r13,8)
movapsx  %xmm3, 0x30(%r15,%r13,8)

使用数组的-O3汇编代码(循环部分):

movapsx  (%rbx,%rcx,8), %xmm0
movapsx  0x10(%rbx,%rcx,8), %xmm1
movapsx  0x20(%rbx,%rcx,8), %xmm2
movapsx  0x30(%rbx,%rcx,8), %xmm3
mulpdx  (%rsi,%rcx,8), %xmm0
mulpdx  0x10(%rsi,%rcx,8), %xmm1
mulpdx  0x20(%rsi,%rcx,8), %xmm2
mulpdx  0x30(%rsi,%rcx,8), %xmm3
movapsx  %xmm0, (%r13,%rcx,8)
movapsx  %xmm1, 0x10(%r13,%rcx,8)
movapsx  %xmm2, 0x20(%r13,%rcx,8)
movapsx  %xmm3, 0x30(%r13,%rcx,8)

修改 我已更新代码以测试更多案例:

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


using namespace std;

int main(int argc, char** argv)
{

  int n = 100;
  int N = 10000000;

  // timing for vector calculations
  {
    std::vector<double> a(n),b(n),c(n);

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    {
    TestTimer t("vector1");
    for ( long int j = 0; j < N; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];
    }
  }

  // timing for vector calculations
  {
    std::vector<double> a(n),b(n),c(n);

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    {
    TestTimer t("vector2");
    for ( long int j = 0; j < N; j ++ )
    for ( int i = 0; i < n; i ++ )
      a[i]=b[i]*c[i];
    }
  }

  // timing for array calculations
  {
    double a[n],b[n],c[n];

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    {
    TestTimer t("array");
    for ( long int j = 0; j < N; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];  
    }
  }

  // timing for malloc calculations
  {
    double *a,*b,*c;

    a=(double*)malloc(sizeof(double)*n);
    b=(double*)malloc(sizeof(double)*n);
    c=(double*)malloc(sizeof(double)*n);

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    {
    TestTimer t("malloc");
    for ( long int j = 0; j < N; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];
    }
  }

  // timing for new pointer calculations
  {
    double *a,*b,*c;

    a=new double[n];
    b=new double[n];
    c=new double[n];

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    {
    TestTimer t("new pointer");
    for ( long int j = 0; j < N; j ++ )
    for ( int i = 0; i < n; i ++ )
      aa[i]=bb[i]*cc[i];
    }
  }

}

对于n = 100,N = 10000000我得到:

g++ test.C -o test.x -O3 -Fa -g 
./test.x
vector1 completed in 0.487 seconds
vector2 completed in 0.504 seconds
array completed in 1.624 seconds
malloc completed in 0.409 seconds
new pointer completed in 0.502 seconds

icpc test.C -o test.x -O3 -Fa -g 
./test.x
vector1 completed in 0.318 seconds
vector2 completed in 0.319 seconds
array completed in 0.216 seconds
malloc completed in 0.295 seconds
new pointer completed in 0.289 seconds

对于n = 100000,N = 10000,我得到:

g++ test.C -o test.x -O3 -Fa -g 
./test.x
vector1 completed in 0.699 seconds
vector2 completed in 0.648 seconds
array completed in 0.397 seconds
malloc completed in 0.428 seconds
new pointer completed in 0.464 seconds

icpc test.C -o test.x -O3 -Fa -g 
./test.x
vector1 completed in 0.632 seconds
vector2 completed in 0.616 seconds
array completed in 0.308 seconds
malloc completed in 0.357 seconds
new pointer completed in 0.322 seconds

3 个答案:

答案 0 :(得分:3)

除了运行析构函数和释放内存所花费的时间之外,你还没有测量与<{1}} 相关的任何,这不是必须的完成数组。

尝试将计时器放入新范围,因此在销毁向量之前停止计时,您应该看到非常相似的时间:

std::vector

原因是{ TestTimer t("vector"); for ( long int j = 0; j < 1000; j ++ ) for ( int i = 0; i < n; i ++ ) aa[i]=bb[i]*cc[i]; } 里面只有一个数组,所以通过获取第一个元素的地址并对该数组执行所有计算,你没有对向量做任何事情。完全毫无意义的测试。

如果您测试了std::vector 将显示使用向量时是否存在差异。

目标代码的微小差异可能是因为编译器不够聪明,无法告诉a[i] = b[i] * c[i]&a[0]以及&b[0]不要当这些地址来自向量的起始指针时,它们会互为别名,而它可以告诉堆栈中声明的数组。

答案 1 :(得分:0)

无论你的测量是毫无意义的,人们都可以用以下方式回答你的问题:&#34;是的,但没关系,并且它无论如何也无法比较&#34;。

vector确实&#34;更慢&#34;大部分时间,但它几乎不重要,原因是因为vector和C风格的数组完全不同,它们会做不同的事情。

一个数组被分配一次并永远保持相同的大小(直到你释放它)。这种分配可以在堆栈上发生(但不需要)。另一方面,{em>必须必须在堆上分配它的存储,这不是一个自由操作(特别是释放内存相比,在堆栈上分配时是自动的 - 在通常的情况下它是免费的,在最坏的情况下需要一个寄存器增量。) 没有额外的&#34;智能&#34;内置到数组中。没有安全网。推出比适合的更多的元素,你有UB。

vector管理中的元素的存储,提供可变大小的,当您推送更多元素时,根据需要重新分配和复制元素,所有这些都是透明的,没有您即使知道它发生了。除非你作弊和过度分配,或者你实现vector幕后为你做的同样的工作,否则这些都不可能用C风格的数组。此时你的数组变得和vector一样慢(而且很可能更慢,除非你是一个非常有经验的程序员)。
就目前而言,比较两者并非完全不可能有意义,他们完全不同。只有巧合(好吧,不是巧合,真的......但是你得到了我),你可以在两个文件中存储对象并使用vector访问它们。

分配/重新分配很少发生(并且已经摊销,并且在大多数情况下可以通过正确使用operator[]来避免),所以尽管它们会使reserve&#34;更慢&#34;在正常情况下,它们对整体性能几乎无关紧要。只有你分配和释放数以千计的vector和/或如果你以极其错误的方式使用vector,这才真正重要。

&#34; 99.9%的重要&#34;部件,例如,访问给定索引处的随机元素vector完全的速度与C风格的数组相同。当然,在调试版本中,大多数vector实现都会执行额外的边界检查。但这又是一个有用的额外功能,当然你必须付出代价。在发布版本中,开销消失了。

答案 2 :(得分:0)

除非您的机器比我的AMD Phenom II快100倍,否则我怀疑您的优化代码已完全消除了这两个循环。因为某种原因,我正在使用的clang ++(来自几周前的3.6)为VLA做了这个,但不是为了向量。不完全确定原因。

如果我添加代码实际使用aa中的计算值,那么优化编译的结果几乎相同(并且在使用C ++向量时测量非优化代码的性能不正确,除非您实际打算提供您的生产代码,没有优化!)。通过优化(-O3),结果几乎相同:

vector completed in 0.286 seconds
x = 9.99985e+14
array completed in 0.284 seconds
x = 9.99985e+14

我运行了几次,第二个(数组)变体总是快1-3毫秒,所以约占总时间的1%。

这是我的代码:

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

using namespace std;


int main(int argc, char** argv)
{
  double x = 0;
  // timing for vector calculations
  {
    int n = 100000;
    std::vector<double> a(n),b(n),c(n);

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    for(int i = 0; i < n; i++)
    {
    bb[i] = i * 2;
    cc[i] = i * 1.5;
    }

    TestTimer t("vector");
    for ( long int j = 0; j < 1000; j ++ )
    for ( int i = 0; i < n; i ++ )
        aa[i]=bb[i]*cc[i];

    for(auto v : a)
    {
    x += v;
    }
  }
  cout << "x = " << x << endl;

  // timing for array calculations
  {
    int n = 100000;
    double a[n],b[n],c[n];

    double* aa = &a[0];
    double* bb = &b[0];
    double* cc = &c[0];

    for(int i = 0; i < n; i++)
    {
    bb[i] = i * 2;
    cc[i] = i * 1.5;
    }

    TestTimer t("array");
    for ( long int j = 0; j < 1000; j ++ )
    for ( int i = 0; i < n; i ++ )
        aa[i]=bb[i]*cc[i];

    x = 0;
    for(auto v : a)
    {
    x += v;
    }
  }
  cout << "x = " << x << endl;

}