Hello world!
月度归档:2016年06月
STL容器 vector list 性能对比(附带测试方法和测试数据)
最近在重构公司的一个C++模块,逻辑里有排序、过滤等操作。开发过程中,遇到下面的一些问题:
问题和猜想
- 单链表的归并排序和线性表的快速排序的时间复杂度都是 O(nlog(n)),但在实际情况中哪个更快呢?需要测试一下。
- 猜想的发散,stl vector 和 list(双向链表)的迭代器遍历、顺序插入、clear操作, 时间复杂度好像都是相同的,但谁更快呢?
- 看stl代码去研究vector和list的实现,并不能得到我们最终想要的结果,测试数据会更直观。
测试总结
先写测试总结,测试方法、测试结果都在下面。
- 迭代器遍历,list 比vector 稍块,使用for_each 比使用for更快。
- 顺序插入, vector比list 约快3倍
- clear操作,vector 几乎不耗时,list 要耗费好多时间,vector比list至少快1千倍以上
- 排序, vector 大约比list 快2倍。
结合项目,对比vector和list的性能
- 在我重构前,模块使用的是vector存储数据,排序使用的是快排(大于16小于堆),过滤则是用临时数据vector转存。
- 在我重构后,使用list存储,排序使用list的归并排序,过滤则是直接使用list的erase操作。
- 重构前,耗时4000微秒,重构后1500微秒,时间减少60%。
- list除了删除操作和遍历操作,排序和插入都比vector慢,但是我使用list后,却提升了性能。原因可能有2个: 1.单个元素数据结构大,vector移动这些元素代价较大,而list移动这些元素反而代价很小;2.去掉了中间临时存储,数据的转存代价也比较大。
- 使用list后,模块的可扩展性也变得更好了。
这次项目最大的收获就是,如果有过滤操作,优选使用list。vector的缺点如下,1.vector快速排序需要移动元素,如果元素占据空间大,移动代价也非常大。2.vector过滤需要借助中间临时存储,直接erase的代价更大。
测试方法
- 随机生成10万数据量,由vector和list结构的变量分别存储
- 对vector 和 list 的数据分别做如下, 1)迭代器遍历 2)顺序插入 3) clear操作 4)排序
- 记录每种操作消耗的时间
- 多次测试,记录典型的数据结果
测试结果
直接列出测试结果,单位是微秒
| vector for_each : | 3263 |
| list for_each : | 2556 |
| vector traverse : | 4783 |
| vector num traverse : | 666 |
| list traverse : | 3078 |
| vector push_back : | 8136 |
| list push_back : | 24581 |
| list delete : | 7428 |
| vector push_back : | 7329 |
| list push_back : | 22968 |
| vector clear : | 0 |
| list clear : | 13079 |
| vector sort : | 89512 |
| list sort : | 146529 |
(附)测试程序
Github 托管地址,保持更新:https://github.com/zuocheng-liu/code-samples/blob/master/linux-c/stl/vector_list_performance_test.cpp
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include <list>
#include <vector>
#include <iostream>
#define MAX_NUM 100000
using namespace std;
timeval tv;
uint64_t timer;
bool NumCmp(uint32_t a, uint32_t b) { return (a > b); }
void startTimer() {
gettimeofday(&tv,NULL);
timer = 1000000 * tv.tv_sec + tv.tv_usec;
}
uint64_t stopTimer() {
gettimeofday(&tv,NULL);
timer = 1000000 * tv.tv_sec + tv.tv_usec - timer;
return timer;
}
int main() {
vector<uint32_t> v;
vector<uint32_t> v2;
list<uint32_t> l;
list<uint32_t> l2;
for (int i = 0; i< MAX_NUM; ++i) {
srand((int)time(0) * i * i + 1);
int num = rand();
v.push_back(num);
l.push_back(num);
}
// compare oper traverse
startTimer();
for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
*iter;
}
cout<<"vector\t traverse\t :\t"<< stopTimer() << endl;
startTimer();
for (int i = 0 ; i < MAX_NUM; ++ i) {
//v[i];
}
cout<<"vector\t num traverse\t :\t"<< stopTimer() << endl;
startTimer();
for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
}
cout<<"list\t traverse\t :\t"<< stopTimer() << endl;
// compare oper push_back
startTimer();
for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
v2.push_back(*iter);
}
cout<<"vector\t push_back\t :\t"<< stopTimer() << endl;
startTimer();
for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
l2.push_back(*iter);
}
cout<<"list\t push_back\t :\t"<< stopTimer() << endl;
// compare oper delete
startTimer();
v2.clear();
for (vector<uint32_t>::iterator iter = v2.begin(); iter != v2.end(); ++ iter) {
// v2.erase(iter);
}
//cout<<"vector\t delete\t :\t"<< stopTimer() << endl;
startTimer();
for (list<uint32_t>::iterator iter = l2.begin(); iter != l2.end(); ++ iter) {
iter = l2.erase(iter);
}
cout<<"list\t delete\t :\t"<< stopTimer() << endl;
// compare oper push_back
startTimer();
for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
v2.push_back(*iter);
}
cout<<"vector\t push_back\t :\t"<< stopTimer() << endl;
startTimer();
for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
l2.push_back(*iter);
}
cout<<"list\t push_back\t :\t"<< stopTimer() << endl;
// compare oper clear
startTimer();
v2.clear();
cout<<"vector\t clear \t:\t"<< stopTimer() << endl;
startTimer();
l2.clear();
cout<<"list\t clear \t :\t"<< stopTimer() << endl;
// compare oper sort
startTimer();
std::sort(v.begin(), v.end(), NumCmp);
cout<<"vector\t sort \t :\t"<< stopTimer() << endl;
startTimer();
l.sort(NumCmp);
cout<<"list\t sort \t :\t"<< stopTimer() << endl;
return 0;
}
C/C++ 中如何写“空语句”
最近我的同事和一些网友都说C/C++中“空语句”(就是单独一个分号的语句)具有延时的作用,可以用来写延时代码。其实这是一种错误的理解。
首先,有人认为空语句经编译后,生成汇编代码是“NOP”指令,NOP指令是空操作指令,执行一个指令周期时间,所以认为C/C++中的“空语句”还有延时的功能,其实这是错误的,“空语句”是不会生成任何有效的指令代码的,是不具有延时做用的。
有人说如下代码是具有延时做用,实际上下边的延时功能主要是加法运算和条件判断运算指令起到了延时的作用。
define DELAY asm(“nop”);
Google C++ 代码风格示例
They say a good example is worth 100 pages of API documentation, a million directives, or a thousand words.
最近项目中决定使用Google的C++代码规范,然后写了两个代码示例文件(.h 和 .cc),这两个文件中的代码都是用Google的C++代码规范。
写示例代码的好处
- 减少查看文档时间,降低学习成本
- 帮助程序员快速了解Google的代码风格的概貌
- 用于参考和模仿,帮助快速上手
代码托管地址
下面地址保持持续更新:
https://github.com/zuocheng-liu/code-samples/tree/master/code_style
仓促之作,错误和疏忽不可避免,但保证后续不断更新。
初版代码示例
建议点击上面的链接,获取最新版。编辑器的原因,缩进的显示可能有问题。
// Copyright (c) 2016, Zuocheng.net
//
// Author: Zuocheng Liu
//
// Lisence: GPL
//
// File: code_samples/code_style/google_code_style.h
// (文件名要全部小写,可以包含下划线(_)或短线(-))
// C++文件以.cc 结尾,头文件以.h 结尾。
//
// Google C++ code Style 代码示例 (简体中文版)
//
// *注释 使用//或/* */,但要统一。
// *注释 每一个文件版权许可及作者信息后,对文件内容进行注释说明
// -------------------------------------------------------------最多不能超过80行
#ifndef CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE_
#define CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE_
// 命名空间的名称是全小写的,其命名基于项目名称和目录结构
// 不要声明命名空间std 下的任何内容
namespace code_samples {
namespace code_style {
// **变量命名**
// 尽可能给出描述性名称,不要节约空间
// 变量名一律小写,单词间以下划线相连
// 少用全用变量,可以以g_与局部变量区分
int g_my_exciting_global_variable;
// 只有在描述数据时用struct ,其他情况都用class
typedef struct CodeStyle {
uint32_t type;
} CodeStyle;
// **类注释**
// 每个类的定义要附着描述类的功能和用法的注释
//
// GoogleCodeStyle , 通过代码示例,展现google c++ code style
//
// (按需注明synchronization assumptions,是否线程安全)
class GoogleCodeStyle {
public :
// ** 声明次序 **
// - typedefs 和enums;
// - 常量;
// - 构造函数;
// - 析构函数;
// - 成员函数,含静态成员函数;
// - 数据成员,含静态数据成员。
// **类型命名**
// 每个单词以大写字母开头,不包含下划线
// 所有类型命名——类、结构体、类型定义(typedef)、枚举——使用相同约定
typedef enum StyleType {
// 枚举值应全部大写,单词间以下划线相连
GOOGLE_CODE_STYLE = 0,
K_AND_R,
POCO
} StyleType;
// 常量命名,在名称前加k
const int kDaysInAWeek = 7;
GoogleCodeStyle() {}
~GoogleCodeStyle() {}
// **函数注释**
// 函数声明处注释描述函数功能,定义处描述函数实现.
// - inputs(输入)及outputs(输出);
// - 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;
// - 如果函数分配了空间,需要由调用者释放;
// - 参数是否可以为NULL;
// - 是否存在函数使用的性能隐忧(performance implications);
// - 如果函数是可重入的(re-entrant),其同步前提(synchronization assumptions)
// 举例 :
// 初始化函数
~Init() {}
// 普通函数名以大写字母开头,每个单词首字母大写,没有下划线
//
uint32_t MyExcitingMethod(CodeStype &code_style, char *output);
// 存取函数要与存取的变量名匹配
inline int num_entries() const { return num_entries_; }
inline void set_num_entries(int num_entries) { num_entries_ = num_entries; }
// 类的成员变量以下划线结尾
int num_completed_connections_;
protected :
private :
// **类数据成员**
// 每个类数据成员(也叫实例变量或成员变量)应注释说明用途。
// 如果变量可以接受NULL 或-1, 等警戒值(sentinel values),须说明之
int num_entries_;
}; // class GoogleCodeStyle
}} // namespace code_samples::code_style
#endif //CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE