C++ 中list、vector和deque比較[轉]


http://blog.csdn.net/xiaolajiao8787/article/details/5882609

 

Ladies & Gentlemem:

大家好,這里是首屆C++模板武道會的現場,本次武道會由beyond_ml做東,第一場解說員為beyond_ml。由於首次舉辦這樣規模空前的盛會,難免有疏漏之處,還請各位高手不吝賜教。Beyond_ml有理啦。同時也歡迎各位大蝦把此次武道會看做是一個虛基類,不斷繼承,派生出新的比賽。

比賽開始:

首先介紹比武參賽者:

Vector:金山詞霸翻譯成:矢量,向量等,C++容器模板中的大哥大,就像是一個加強版的隊列,之所以這樣說,是因為它不但有隊列形式的索引,還能動態的添加擴充。使用尤其廣泛,並且在許多經典教材中被作為重點介紹。

特點:把被包含的對象以數組的形式存儲,支持索引形式的訪問(這種訪問速度奇快無比)。但由此也產生了一個問題,由於數據存儲形式的固定化,你如果想在他中間部位insert對象的話,搞不好會讓你吃盡苦頭。因為他在分配空間的時候,可是成塊分配的連續空間哦。

Deque:英文“double-ended-queue”。名如其人,這是C++有序容器中聞名遐邇的雙向隊列。他在設計之初,就為從兩端添加和刪除元素做了特殊的優化。同樣也支持隨即訪問,也有類似於vector的[ ]操作符,但大家不要因此就把他和vector混為一潭哦。

特點:從本質上講,他在分配內存的時候,使用了MAP的結構和方法。化整為零,分配了許多小的連續空間,因此,從deque兩端添加、刪除元素是十分方便的。最重要的一點:如果在不知道內存具體需求的時候,使用deque絕對是比vector好的,具體怎么好,比武為證。另外在插一句,不知是不是有意設計,大多數情況下,deque和vector是可以互換使用的。

List:模板中的雙向鏈表。設計他的目的可能就是為了在容器中間插入、刪除吧,所以有得比有失,他的隨機訪問速度可不敢恭維。而且沒有[ ]操作。即便你是為了從前到后的順序訪問,也不見得就能快多少,“愛用不用,反正俺有強項!”List還挺哼,這也難怪,看看他的特點吧。

特點:隨機的插入、刪除元素,在速度上占有明顯的優勢。並且,由於內存分配不連續,他對插入的要求也十分的低。所以在使用大對象的時候,這可是一個不錯的選擇。

“閑言碎語不要講,開打,開打。”

“咚……”

比武正式開始:

第一局:比一比誰的內存管理強。

比試方法:人為引起存儲的對象數據溢出,分別看看系統消耗。系統消耗在這里指:對象構造函數、拷貝函數、析構函數的調用次數。

測試程序如下:

noisy.h  …… 包含了測試對象,每次在調用構造、拷貝、析構函數的時候,都會打印相應的提示。

//: C04:Noisy.h

// A class to track various object activities

#ifndef NOISY_H

#define NOISY_H

#include <iostream>

class Noisy

{

       static long create, assign, copycons, destroy;

       long id;

       public:

       Noisy() : id(create++)

       {

              std::cout << "d[" << id << "]";

       }

       Noisy(const Noisy& rv) : id(rv.id)

       {

              std::cout << "c[" << id << "]";

              copycons++;

       }

       Noisy& operator=(const Noisy& rv)

       {

              std::cout << "(" << id << ")=[" <<

              rv.id << "]";

              id = rv.id;

              assign++;

              return *this;

       }

       friend bool

       operator<(const Noisy& lv, const Noisy& rv)

       {

              return lv.id < rv.id;

       }

       friend bool

       operator==(const Noisy& lv, const Noisy& rv)

       {

              return lv.id == rv.id;

       }

       ~Noisy()

       {

              std::cout << "~[" << id << "]";

              destroy++;

       }

       friend std::ostream&

       operator<<(std::ostream& os, const Noisy& n)

       {

              return os << n.id;

       }

       friend class NoisyReport;

};

struct NoisyGen

{

       Noisy operator()() { return Noisy(); }

};

// A singleton. Will automatically report the

// statistics as the program terminates:

class NoisyReport

{

       static NoisyReport nr;

       NoisyReport() {} // Private constructor

       public:

       ~NoisyReport()

       {

              std::cout << "/n-------------------/n"

              << "Noisy creations: " << Noisy::create

              << "/nCopy-Constructions: "

              << Noisy::copycons

              << "/nAssignments: " << Noisy::assign

              << "/nDestructions: " << Noisy::destroy

              << std::endl;

       }

};

// Because of these this file can only be used

// in simple test situations. Move them to a

// .cpp file for more complex programs:

long Noisy::create = 0, Noisy::assign = 0,

Noisy::copycons = 0, Noisy::destroy = 0;

NoisyReport NoisyReport::nr;

#endif // NOISY_H ///:~

目標:插入一千個Noisy對象。

Vector上場 

//: C04:VectorOverflow.cpp

// Shows the copy-construction and destruction

// That occurs when a vector must reallocate

// (It maintains a linear array of elements)

#include "noisy.h"

#include <vector>

#include <iostream>

#include <string>

#include <cstdlib>

using namespace std;

int main(int argc, char* argv[])

{

       int size = 1000;

      if(argc >= 2) size = atoi(argv[1]);

       vector<Noisy> vn;

       Noisy n;

       for(int i = 0; i < size; i++)

       vn.push_back(n);

       cout << "/n cleaning up /n";

} ///:~

測試結果:

Noisy creations: 1                     (構造函數調用)

Copy-Constructions: 2023                     (拷貝函數調用)

Assignments: 0                                   (賦值調用)

Destructions: 2024                                   (析構調用)

Beyond_ml評論:哇!老大,我只是插一千個對象而已,你怎么一下子調2023次拷貝函數,相應的還有2024次析構調用,哎,看來,如果你插入的數據超過了他的保留空間后,vector搬家的動靜是很大的。

Deque上場

代碼部分可以照抄vector的,因為他們太像了。

測試結果:

Noisy creations: 1

Copy-Constructions: 1007

Assignments: 0

Destructions: 1008

Beyond_ml評論:嗯,不錯。不過那多出來的7個也不太好么。

List上場

代碼部分繼續照抄。

測試結果:

Noisy creations: 1

Copy-Constructions: 1000

Assignments: 0

Destructions: 1001

Beyond_ml評論:perfect!非常好!滿分。

第一局結束List勝出!

第二局 比一比隨機訪問的速度(訪問速度以時鍾周期作為標准)

咦?話音剛落,list怎么就舉了白旗?哦,我想起來了,他不支持隨機訪問策略。也就是沒有[ ]和at()操作。

測試程序:IndexingVsAt.cpp 插入一千個數據,用[ ]和at( )兩種方法隨機訪問一百萬次,比較時鍾周期。

//: C04:IndexingVsAt.cpp

// Comparing "at()" to operator[]

#include <vector>

#include <deque>

#include <iostream>

#include <ctime>

using namespace std;

int main(int argc, char* argv[])

{

       long count = 1000;

       int sz = 1000;

      if(argc >= 2) count = atoi(argv[1]);

      if(argc >= 3) sz = atoi(argv[2]);

       vector<int> vi(sz);

       clock_t ticks = clock();

       for(int i1 = 0; i1 < count; i1++)

       for(int j = 0; j < sz; j++)

       vi[j];

       cout << "vector[]" << clock() - ticks << endl;

       ticks = clock();

       for(int i2 = 0; i2 < count; i2++)

       for(int j = 0; j < sz; j++)

       vi.at(j);

       cout << "vector::at()" << clock()-ticks <<endl;

       deque<int> di(sz);

       ticks = clock();

       for(int i3 = 0; i3 < count; i3++)

       for(int j = 0; j < sz; j++)

       di[j];

       cout << "deque[]" << clock() - ticks << endl;

       ticks = clock();

       for(int i4 = 0; i4 < count; i4++)

       for(int j = 0; j < sz; j++)

       di.at(j);

       cout << "deque::at()" << clock()-ticks <<endl;

       // Demonstrate at() when you go out of bounds:

       //di.at(vi.size() + 1); error here.

} ///:~

測試結果:

vector[]360000

vector::at()790000

deque[]1350000

deque::at()1750000

beyond_ml評論:果然是不必不知道,一比嚇一跳。Vector以絕對優勢勝出!

第三局 比后部插入速度以及iterator的訪問速度

插入方法主要使用push_back。

然后再通過內部的iterator指針完成取數據的操作。

測試文件:

require.h  主要包含了一些文件操作。

//: :require.h

// Test for error conditions in programs

// Local "using namespace std" for old compilers

#ifndef REQUIRE_H

#define REQUIRE_H

#include <cstdio>

#include <cstdlib>

#include <fstream>

inline void require(bool requirement,const char* msg = "Requirement failed")

{

       using namespace std;

       if (!requirement)

       {

              fputs(msg, stderr);

              fputs("/n", stderr);

              exit(1);

       }

}

inline void requireArgs(int argc, int args,const char* msg = "Must use %d arguments")

{

       using namespace std;

       if (argc != args + 1)

       {

              fprintf(stderr, msg, args);

              fputs("/n", stderr);

              exit(1);

       }

}

inline void requireMinArgs(int argc, int minArgs,const char* msg ="Must use at least %d arguments")

{

       using namespace std;

       if(argc < minArgs + 1)

       {

              fprintf(stderr, msg, minArgs);

              fputs("/n", stderr);

              exit(1);

       }

}

inline void assure(std::ifstream& in,const char* filename = "")

{

       using namespace std;

       if(!in)

       {

              fprintf(stderr,    "Could not open file %s/n", filename);

              exit(1);

       }

}

inline void assure(std::ofstream& in,const char* filename = "")

{

       using namespace std;

       if(!in)

       {

              fprintf(stderr,    "Could not open file %s/n", filename);

              exit(1);

       }

}

#endif

StringDeque.cpp 測試主程序

//: C04:StringDeque.cpp

// Converted from StringVector.cpp

#include "require.h"

#include <string>

#include <deque>

#include <vector>

#include <list>

#include <fstream>

#include <iostream>

#include <iterator>

#include <sstream>

#include <ctime>

using namespace std;

int main(int argc, char* argv[])

{

       requireArgs(argc, 1);

       ifstream in(argv[1]);

       assure(in, argv[1]);

       vector<string> vstrings;

       deque<string> dstrings;

       list<string> lstrings;

       string line;

      

       // Time reading into vector:

       clock_t ticks = clock();

       while(getline(in, line))

       vstrings.push_back(line);

       ticks = clock() - ticks;

       cout << "Read into vector: " << ticks << endl;

      

       // Repeat for deque:

       ifstream in2(argv[1]);

       assure(in2, argv[1]);

       ticks = clock();

       while(getline(in2, line))

       dstrings.push_back(line);

       ticks = clock() - ticks;

       cout << "Read into deque: " << ticks << endl;

      

       // Repeat for list:

       ifstream in3(argv[1]);

       assure(in3, argv[1]);

       ticks = clock();

       while(getline(in3, line))

       lstrings.push_back(line);

       ticks = clock() - ticks;

       cout << "Read into list: " << ticks << endl;

      

       // Compare iteration

       ofstream tmp1("tmp1.tmp"), tmp2("tmp2.tmp"), tmp3("tmp3.tmp");

      

       ticks = clock();

       copy(vstrings.begin(), vstrings.end(),

       ostream_iterator<string>(tmp1, "/n"));

       ticks = clock() - ticks;

       cout << "Iterating vector: " << ticks << endl;

      

       ticks = clock();

       copy(dstrings.begin(), dstrings.end(),

       ostream_iterator<string>(tmp2, "/n"));

       ticks = clock() - ticks;

       cout << "Iterating deqeue: " << ticks << endl;

      

       ticks = clock();

       copy(lstrings.begin(), lstrings.end(),

       ostream_iterator<string>(tmp3, "/n"));

       ticks = clock() - ticks;

       cout << "Iterating list: " << ticks << endl;

      

} ///:~

測試用的文件是一個三千行的文本。

測試結果:

Read into vector: 690000

Read into deque: 680000

Read into list: 690000

Iterating vector: 20000

Iterating deqeue: 20000

Iterating list: 10000

測試用的文件是一個二千行的文本。

Read into vector: 460000

Read into deque: 460000

Read into list: 440000

Iterating vector: 10000

Iterating deqeue: 10000

Iterating list: 20000

測試用的文件是一個一千行的文本。

測試結果:

Read into vector: 230000

Read into deque: 240000

Read into list: 250000

Iterating vector: 10000

Iterating deqeue: 0

Iterating list: 10000

Beyond_ml的評論:這下就難了,怎么說呢?

在push_back的時候,顯然文件越小,vector越占優,文件越大,list越占優。哈哈,開玩笑,如果作研究的都像我這樣,那大家都不要干了,其實,這是和上面幾個測試的結果分不開的,文件越大,vector越費力,原因很簡單,他要不停的開辟新的內存空間來給自己搬家,而deque就好的多,因為他不必搬家,他只是需要小范圍的重新排列。而list就更每問題了,他的內存空間本來就是離散的。這下你能明白了吧?

所以作為函數本身的運行速度是沒有大差別的,但現在看來,如果牽扯上其它因素,就要令說了。

而讀數據的速度來看,list的表現十分讓人迷惑不解對此,我還想不到什么好的解釋,也許和程序運行時主機的內存狀態有關吧。Vector和list的表現可以說是不分伯仲,但我個人的觀點是vector肯定要好一些,因為他的內存是連續的。

所以第三局,三者的表現各有千秋。

為了節省時間和空間,下面這個程序將系統測試后面的所有項目。然后根據具體的結果分析各個參賽選手的性能差異。

SequencePerformance.cpp

//: C04:SequencePerformance.cpp

// Comparing the performance of the basic

// sequence containers for various operations

#include <vector>

#include <queue>

#include <list>

#include <iostream>

#include <string>

#include <typeinfo>

#include <ctime>

#include <cstdlib>

using namespace std;

class FixedSize

{

int x[20];

// Automatic generation of default constructor,

// copy-constructor and operator=

} fs;

template<class Cont>

struct InsertBack

{

       void operator()(Cont& c, long count)

       {

              for(long i = 0; i < count; i++)

              c.push_back(fs);

       }

       char* testName() { return "InsertBack"; }

};

template<class Cont>

struct InsertFront

{

       void operator()(Cont& c, long count)

       {

              long cnt = count * 10;

              for(long i = 0; i < cnt; i++)

              c.push_front(fs);

       }

       char* testName() { return "InsertFront"; }

};

template<class Cont>

struct InsertMiddle

{

       void operator()(Cont& c, long count)

       {

              typename Cont::iterator it;

              long cnt = count / 10;

              for(long i = 0; i < cnt; i++)

              {

                     // Must get the iterator every time to keep

                     // from causing an access violation with

                     // vector. Increment it to put it in the

                     // middle of the container:

                     it = c.begin();

                     it++;

                     c.insert(it, fs);

              }

       }

       char* testName() { return "InsertMiddle"; }

};

template<class Cont>

struct RandomAccess

{ // Not for list

       void operator()(Cont& c, long count)

       {

              int sz = c.size();

              long cnt = count * 100;

              for(long i = 0; i < cnt; i++)

              c[rand() % sz];

       }

       char* testName() { return "RandomAccess"; }

};

template<class Cont>

struct Traversal

{

       void operator()(Cont& c, long count)

       {

              long cnt = count / 100;

              for(long i = 0; i < cnt; i++)

              {

                     typename Cont::iterator it = c.begin(),

                     end = c.end();

                     while(it != end) it++;

              }

       }

       char* testName() { return "Traversal"; }

};

template<class Cont>

struct Swap

{

       void operator()(Cont& c, long count)

       {

              int middle = c.size() / 2;

              typename Cont::iterator it = c.begin(),

              mid = c.begin();

              it++; // Put it in the middle

              for(int x = 0; x < middle + 1; x++)

              mid++;

              long cnt = count * 10;

              for(long i = 0; i < cnt; i++)

              swap(*it, *mid);

       }

       char* testName() { return "Swap"; }

};

template<class Cont>

struct RemoveMiddle

{

       void operator()(Cont& c, long count)

       {

              long cnt = count / 10;

              if(cnt > c.size())

              {

                     cout << "RemoveMiddle: not enough elements"

                     << endl;

                     return;

              }

              for(long i = 0; i < cnt; i++)

              {

                     typename Cont::iterator it = c.begin();

                     it++;

                     c.erase(it);

              }

       }

       char* testName() { return "RemoveMiddle"; }

};

template<class Cont>

struct RemoveBack

{

       void operator()(Cont& c, long count)

       {

              long cnt = count * 10;

              if(cnt > c.size())

              {

                     cout << "RemoveBack: not enough elements"

                     << endl;

                     return;

              }

              for(long i = 0; i < cnt; i++)

              c.pop_back();

       }

       char* testName() { return "RemoveBack"; }

};

template<class Op, class Container>

void measureTime(Op f, Container& c, long count)

{

       string id(typeid(f).name());

       bool Deque = id.find("deque") != string::npos;

       bool List = id.find("list") != string::npos;

       bool Vector = id.find("vector") !=string::npos;

       string cont = Deque ? "deque" : List ? "list"

       : Vector? "vector" : "unknown";

       cout << f.testName() << " for " << cont << ": ";

       // Standard C library CPU ticks:

       clock_t ticks = clock();

       f(c, count); // Run the test

       ticks = clock() - ticks;

       cout << ticks << endl;

}      

typedef deque<FixedSize> DF;

typedef list<FixedSize> LF;

typedef vector<FixedSize> VF;

int main(int argc, char* argv[])

{

       srand(time(0));

       long count = 1000;

      if(argc >= 2) count = atoi(argv[1]);

       DF deq;

       LF lst;

       VF vec, vecres;

       vecres.reserve(count); // Preallocate storage

       measureTime(InsertBack<VF>(), vec, count);

       measureTime(InsertBack<VF>(), vecres, count);

       measureTime(InsertBack<DF>(), deq, count);

       measureTime(InsertBack<LF>(), lst, count);

       // Can't push_front() with a vector:

       //! measureTime(InsertFront<VF>(), vec, count);

       measureTime(InsertFront<DF>(), deq, count);

       measureTime(InsertFront<LF>(), lst, count);

       measureTime(InsertMiddle<VF>(), vec, count);

       measureTime(InsertMiddle<DF>(), deq, count);

       measureTime(InsertMiddle<LF>(), lst, count);

       measureTime(RandomAccess<VF>(), vec, count);

       measureTime(RandomAccess<DF>(), deq, count);

       // Can't operator[] with a list:

       //! measureTime(RandomAccess<LF>(), lst, count);

       measureTime(Traversal<VF>(), vec, count);

       measureTime(Traversal<DF>(), deq, count);

       measureTime(Traversal<LF>(), lst, count);

       measureTime(Swap<VF>(), vec, count);

       measureTime(Swap<DF>(), deq, count);

       measureTime(Swap<LF>(), lst, count);

       measureTime(RemoveMiddle<VF>(), vec, count);

       measureTime(RemoveMiddle<DF>(), deq, count);

       measureTime(RemoveMiddle<LF>(), lst, count);

       vec.resize(vec.size() * 10); // Make it bigger

       measureTime(RemoveBack<VF>(), vec, count);

       measureTime(RemoveBack<DF>(), deq, count);

       measureTime(RemoveBack<LF>(), lst, count);

} ///:~

第四局 向前插入

vector棄權。他不支持push_front操作。

測試結果:

InsertFront for deque: 20000

InsertFront for list: 30000

Deque獲勝。

Beyond_ml評論:毫不意外,deque的看家本領當然了得。

第五局 中間插入

測試結果:

InsertMiddle for vector: 40000

InsertMiddle for deque: 0

InsertMiddle for list: 0

Beyond_ml評論:難為vector了,在任何情況下,vector都不適合中間插入。同時我要為deque唱一把贊歌,能和list打成平手,實在了不起。

第六局 交換數據

測試結果:

Swap for vector: 0

Swap for deque: 10000

Swap for list: 20000

Beyond_ml評論:vector的集群優勢非常適合作內存交換。

第七局 中間刪除

測試結果:

RemoveMiddle for vector: 50000

RemoveMiddle for deque: 0

RemoveMiddle for list: 0

Beyond_ml評論:再次難為vector了,在任何情況下,vector同樣不適合中間刪除。同時我要再為deque唱一把贊歌,又list打成平手,實在了不起。

第八局 后部刪除

測試結果:

RemoveBack for vector: 0

RemoveBack for deque: 0

RemoveBack for list: 20000

Beyond_ml評論:為vector和deque歡呼吧!十分的棒!。

來個總結吧。

比賽項目/參賽選手

Vector

Deque

List

內存管理

Poor

Good

perfect

使用[ ]和at() 操作訪問數據

Very good

Normal

N/A

Iterator的訪問速度

Good

Very good

Good

Push_back操作(后插入)

Good

Good

Good

Push_front操作(前插入)

N/A

Very good

Good

Insert(中間插入)

Poor

Perfect

Perfect

Erase(中間刪除)

Poor

Perfect

Perfect

Pop_back(后部刪除)

Perfect

Perfect

Normal

Swap(交換數據)

Perfect

Very good

Good

遍歷

Perfect

Good

Normal

 

哦,好像結束了,其實沒有,我們還有很多事可以作!例如在使用vector的時候,我們能預先reserve足夠的空間,使用效率將成倍提高!另外,他們也並不是設計的一模一樣,他們每一個都有自己獨有的絕跡,如果能讓他們充分發揮,你的程序想來也將上一個檔次。讓我們共同努力吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM