默认分类 2009-03-10 17:55:48 阅读67 评论0 字号:大中小 订阅
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
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
#include
#include
#include
using namespace std;
int main(int argc, char* argv[])
{
int size = 1000;
if(argc >= 2) size = atoi(argv[1]);
vector
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
#include
#include
#include
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
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 < deque 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 < // 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 #include #include 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 #include #include #include #include #include #include #include #include using namespace std; int main(int argc, char* argv[]) { requireArgs(argc, 1); ifstream in(argv[1]); assure(in, argv[1]); vector deque list 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 ticks = clock() - ticks; cout << "Iterating vector: " << ticks << endl; ticks = clock(); copy(dstrings.begin(), dstrings.end(), ostream_iterator ticks = clock() - ticks; cout << "Iterating deqeue: " << ticks << endl; ticks = clock(); copy(lstrings.begin(), lstrings.end(), ostream_iterator 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 #include #include #include #include #include #include #include using namespace std; class FixedSize { int x[20]; // Automatic generation of default constructor, // copy-constructor and operator= } fs; template struct InsertBack { void operator()(Cont& c, long count) { for(long i = 0; i < count; i++) c.push_back(fs); } char* testName() { return "InsertBack"; } }; template 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 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 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 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 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 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 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 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 typedef list typedef vector 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 measureTime(InsertBack measureTime(InsertBack measureTime(InsertBack // Can't push_front() with a vector: //! measureTime(InsertFront measureTime(InsertFront measureTime(InsertFront measureTime(InsertMiddle measureTime(InsertMiddle measureTime(InsertMiddle measureTime(RandomAccess measureTime(RandomAccess // Can't operator[] with a list: //! measureTime(RandomAccess measureTime(Traversal measureTime(Traversal measureTime(Traversal measureTime(Swap measureTime(Swap measureTime(Swap measureTime(RemoveMiddle measureTime(RemoveMiddle measureTime(RemoveMiddle vec.resize(vec.size() * 10); // Make it bigger measureTime(RemoveBack measureTime(RemoveBack measureTime(RemoveBack } ///:~ 第四局 向前插入 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足够的空间,使用效率将成倍提高!另外,他们也并不是设计的一模一样,他们每一个都有自己独有的绝迹,如果能让他们充分发挥,你的程序想来也将上一个档次。让我们共同努力吧。