Linux下map、hash_map、unordered_map性能比较

1,995 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

目录

map介绍:

hash_map介绍:

unordered_map介绍:

测试代码:

测试结果:

当 N = 1,000,000时:

当 N = 10,000,000时:

当 N = 100,000,000时:

测试结论:


map介绍:

map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。

hash_map介绍:

hash_map基于hash table(哈希表)。 哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。

其基本原理是:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。

但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。其插入过程是:

1、得到key 
2、通过hash函数得到hash值 
3、得到桶号(一般都为hash值对桶数求模) 
4、存放key和value在桶内。 
其取值过程是: 
1、得到key 
2、通过hash函数得到hash值 
3、得到桶号(一般都为hash值对桶数求模) 
4、比较桶的内部元素是否与key相等,若都不相等,则没有找到。 
5、取出相等的记录的value。 
hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。这里可以看出,如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候).
由此可见,要实现哈希表, 和用户相关的是:hash函数和比较函数。这两个参数刚好是我们在使用hash_map时需要指定的参数。

unordered_map介绍:

unordered_map也是一种关联容器,存储键值对,并且允许基于键快速检索单个元素。  

在unordered_map中,键值通常用于唯一地标识元素,而映射值是与该键相关联的内容的对象。 键和映射值的类型可能不同。  

在内部,unordered_map中的元素没有在任何特定的顺序排序对关键或映射值,但组织成桶取决于他们的散列值,以便快速访问单个元素直接的键值(平均一个恒定的平均时间复杂度)。  

unordered_map容器通过键访问单个元素的速度比map容器快,尽管它们通常在通过元素子集进行范围迭代时效率较低。  

无序映射实现了直接访问操作符(operator[]),允许使用键值作为参数直接访问映射值。

从C++11开始,哈希表实现已添加到C++标准库标准。决定对类使用备用名称,以防止与这些非标准实现的冲突,并防止在其代码中有hash_table的开发人员无意中使用新类。

所选择的备用名称是unordered_map,它更具描述性,因为它暗示了类的映射接口和其元素的无序性质。

可见hash_mapunordered_map本质是一样的,只不过 unordered_map被纳入了C++标准库标准。

接下来通过代码测试一下三种map的性能与内存使用情况,

测试代码:

/**
比较map、hash_map和unordered_map的执行效率以及内存占用情况
**/
 
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>	
#include <iostream>
#include <fstream>
#include <cstring>
#include <map>
#include <ext/hash_map>
#include <tr1/unordered_map>
 
using namespace std;
 
using namespace __gnu_cxx;
 
using namespace std::tr1;
 
#define N 1000000 //分别测试N=100,000、N=1,000,000、N=10,000,000以及N=100,000,000
 
//分别定义MapKey=map<int,int>、hash_map<int,int>、unordered_map<int,int>
//typedef map<int,int> MapKey;          //采用map
//typedef hash_map<int,int> MapKey;     //采用hash_map
typedef unordered_map<int,int> MapKey;  //采用unordered_map
 
 
 
int GetPidMem(pid_t pid,string& memsize)
{
	char filename[1024];
	
	snprintf(filename,sizeof(filename),"/proc/%d/status",pid);
	
	ifstream fin;
	
	fin.open(filename,ios::in);
	if (! fin.is_open())
	{
		cout<<"open "<<filename<<" error!"<<endl;
		return (-1);
	}
	
	char buf[1024];
	char size[100];
	char unit[100];
	
	while(fin.getline(buf,sizeof(buf)-1))
	{
		if (0 != strncmp(buf,"VmRSS:",6))
			continue;
		
		sscanf(buf+6,"%s%s",size,unit);
		
		memsize = string(size)+string(unit);
	}
	
	fin.close();
	
	return 0;
}
 
int main(int argc, char *argv[])
{
	struct timeval begin;
	
	struct timeval end_insert_;
    struct timeval end_find_;
    struct timeval end_erase_;
		
	MapKey MyMap;
	
	gettimeofday(&begin,NULL);
	
	for(int i=0;i<N;++i)
		MyMap.insert(make_pair(i,i));
	
	gettimeofday(&end_insert_,NULL);
	
	cout<<"insert N="<<N<<",cost="<< (end_insert_.tv_sec-begin.tv_sec) * 1000000 + (end_insert_.tv_usec-begin.tv_usec)<<" usec"<<endl;
	
	for(int i=0;i<N;++i)
		MyMap.find(i);
 
	gettimeofday(&end_find_,NULL);
	
	cout<<"find all N="<<N<<",cost="<<(end_find_.tv_sec-end_insert_.tv_sec) * 1000000 + (end_find_.tv_usec-end_insert_.tv_usec) <<" usec"<<endl;
	
    for(int i=0;i<N;++i)
		MyMap.erase(i);
 
	gettimeofday(&end_erase_,NULL);

    cout<<"erase all N="<<N<<",cost="<<(end_erase_.tv_sec-end_find_.tv_sec) * 1000000 + (end_erase_.tv_usec-end_find_.tv_usec) <<" usec"<<endl;

	string memsize;
	
	GetPidMem(getpid(),memsize);
	
	cout<<memsize<<endl;
	
	return 0;
}
 

 测试代码来源于Linux下map hash_map和unordered_map效率比较_whizchen的博客-CSDN博客_c++ unordered_map,但是测试结果不同,具体如下。

测试结果:

当 N = 1,000,000时:

N = 1000000insert(us)find(us)erase(us)内存(KB)
map84213944056466586649892
hash_map183849334696925946564
unordered_map187592373696869942416

当 N = 10,000,000时:

N = 10000000insert(us)find(us)erase(us)内存(KB)
map1049055853771507647703471708
hash_map1692442312770650502413972
unordered_map2016034345627668341455756

当 N = 100,000,000时:

N = 100000000insert(us)find(us)erase(us)内存(KB)
map12408246670372560915492724690512
hash_map17488308353225070449343914452
unordered_map19799597351992068296804302924

测试结论:

可见map效率最差无疑,但是hash_map与unordered_map在查找和删除时效率相差不大,当数据量逐渐增大时,hash_map的插入性能更好,所以标准库的无序列表性能上跟hash_map相差不大,但是略逊。

内存使用上,一直是map占用最多, unordered_map次之,hash_map占用内存最少。

参考文章:

1、Linux下map hash_map和unordered_map效率比较_whizchen的博客-CSDN博客_c++ unordered_map

2、STL map, hash_map , unordered_map区别、对比 - 云+社区 - 腾讯云