簡體   English   中英

C ++ std :: vector比C99可變長度數組慢嗎?

[英]Is C++ std::vector slower than C99 variable length arrays?

我已經看到了一些有關此的討論。 許多人說他們應該保持相同的速度。 但是我自己做了一些測試。 在我看來,使用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

除了運行它們的析構函數和釋放內存所花費的時間外, 您沒有測量與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內部只有一個數組,因此通過獲取第一個元素的地址並對該數組執行所有計算,您不會對vector做任何操作。 完全沒有意義的測試。

如果您測試a[i] = b[i] * c[i] 則將顯示使用向量時是否存在差異。

目標代碼之間的細微差異可能是由於以下事實:當這些地址來自矢量的地址時,編譯器不夠聰明,無法告訴&a[0]&b[0]&c[0]彼此互不別名。起始指針,而對於堆棧上聲明的數組,它可以告訴我們。

不管您的測量是沒有意義的,您都可以通過以下方式回答您的問題:“是的,但這並不重要,而且無論如何也無法進行比較”。

vector在大多數情況下確實確實是“慢”的,但這並不重要,原因是vector和C樣式數組是完全不同的事情,它們執行不同的操作。

數組分配一次並永久保持相同大小(直到釋放它)。 該分配可以在堆棧上進行(但不需要這樣做)。 另一方面, vector 必須必須在堆上分配其存儲空間,這不是一個自由操作(尤其是與在堆棧上分配時自動分配內存相比,釋放內存的開銷相當大-通常情況下是免費的並在最壞的情況下增加一個寄存器的值)。
數組中沒有其他“智能”。 沒有安全網。 推入更多元素而不適合,您將擁有UB。

vector管理內部元素的存儲提供可變大小並在您按入更多元素時按需重新分配和復制元素,所有這些操作透明地進行,甚至不知道它會發生。 除非您作弊和過度分配,或者實現了vector在幕后為您所做的相同工作,否則使用C樣式的數組根本不可能做到這一點。 在那一點上,數組變得和vector一樣慢(並且可能更慢,除非您是一個非常有經驗的程序員)。
就此而言,以一種有意義的方式對兩者進行比較根本是不可能的,它們做的是完全不同的事情。 只有通過巧合(好吧,不是巧合,真的……但您明白我的意思),您可以將對象存儲在兩個對象中,並使用operator[]訪問。

分配/重新分配很少發生(並且會攤銷,並且在大多數情況下可以通過適當使用reserve來避免),因此盡管它們確實使vector “變慢”,但在正常情況下它們對整體性能幾乎沒有影響。 只有分配和釋放數千個vector和/或以非常錯誤的方式使用vector才真正重要。

“ 99.9%重要”部分(例如,以給定索引訪問隨機元素)的vector速度與C樣式數組的速度完全相同 當然,在大多數vector實現都進行附加邊界檢查的調試版本中。 但這又是有用的額外功能,您當然需要付費。 在發布版本中,開銷消失了。

除非您的計算機比我的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;

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM