浅谈C++容器动态内存管理的优化

在信息学竞赛中,C++的容器的用途非常广泛,但经常因常数过大而超时。怎样才能提高它们的效率呢?

我们知道,容器是存储同一类对象的对象,既然“对象”我们无法改变,那么我们只能从“存储”入手,不难想到,不同容器在实现上的根本区别是它们对应着不同的内存组织方式,内存管理无疑是这种实现的核心,所以优化内存管理是加快容器效率的最好途径之一。


一、内存分配器简介

怎样才能优化内存管理呢?很简单,C++为我们提供了这样的接口,我们可以通过自定义容器模板中的最后一个allocator内存分配器来提高容器效率,在默认情况下,alloc为allocator<T>,allocator<T>是C++的用于泛型化内存分配的模板,它在申请小空间时通常很快(比new运算符快一些),又因为自定义一般化的内存分配器(可自行查询“内存池”的资料)需要较大的代码量,而且效率未必高,所以本文我们只讨论某些特殊情况下的内存分配。


二、编写内存分配模板

下面我们来编写一个简单的内存分配器,为了简单起见,我们继承C++的allocator<T>:

template<typename T>
struct myalloc:allocator<T>

构造函数是必不可少的:

myalloc(){}

template<typename T2>//别忘记写模板,下同

myalloc(const myalloc<T2> &a){}

template<typename T2>

myalloc<T>& operator=(const myalloc<T2> &a)

{

return *this;

}

由于继承了allocator<T>,所以一定要把rebind覆盖掉,否则other分配器将是allocator<T>,而不是myalloc:

template<typename T2>

struct rebind

{

typedef myalloc<T2> other;

};

接下来我们将讨论一下:如何实现T* allocate(size_t n)和void deallocate(T* p,size_t
n)成员函数。我们只研究内存分配的最简单情况:不回收内存分配和定长内存分配。


三、不回收内存分配

回想一下链表的写法,在竞赛中最常见的实现往往是用数组模拟链表,存储链表的空间是从一个足够大的数组中得到的,这样编写快、效率也高。于是我们得到了主要思想:化动为静!我们一次性预留足够大的“线性”空间,申请内存时从“线”的开头截掉一部分,记录开头的位置即可。思路很直观,实现也很简单。

开一个足够大的数组space,用space_len记录已经分配到的空间大小,注意space必须是全局变量,如果写在类里的话,那么这个类每次实例化都会重复占用空间,很可能超过内存限制。

char space[10000000];

int space_len=0;

可以写allocate函数了,n表示需要的空间大小,函数返回申请到的空间首地址:

T* allocate(size_t n)

{

T *result=(T*)(space+space_len);

space_len+=n*sizeof(T);

return result;

}

然后把void deallocate(T* p,size_t n){}留空,短短几行代码就完成了一个不回收的内存分配器。


四、定长内存分配

在要申请的内存长度确定的情况下,我们可以完成内存释放函数以节省内存空间。内存释放的根本目的是将释放的内存用于下一次申请,由于我们要的内存是定长的,所以保存一下刚刚释放掉的内存地址,下次申请时直接返回这个地址不就可以了?我们用栈来保存地址:

int stack[10000000],stack_len=0;

释放很简单:

void deallocate(T* p,size_t n)

{

stack[stack_len++]=(int)p;

}

再修改一下申请函数:

T* allocate(size_t n)

{

if(stack_len!=0)

return (T*)stack[--stack_len];

else

{

T *result=(T*)(space+space_len);

space_len+=n*sizeof(T);

return result;

}

}


五、效率对比

大功告成!分配效率如何呢?这里用list进行效率测试:

int start=clock();

list<int,myalloc<int> > L;

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

{

L.push_back(500);

L.pop_back();

}

cout << "myalloc:" << (double)(clock()-start)/CLOCKS_PER_SEC
<< endl;

start=clock();

list<int> L2;

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

{

L2.push_back(500);

L2.pop_back();

}

cout << "allocator:" << (double)(clock()-start)/CLOCKS_PER_SEC
<< endl;

笔者测试结果:

myalloc:0.063

allocator:0.203

我们的myalloc比默认的allocator快了近三倍!

那么它在实际应用中的效果如何呢?笔者用NOIP2012提高组的drive测试了一下,其中用到了一个list,最后4个点会超时,最慢的一个用时1.33秒,用刚刚介绍的定长内存分配器优化,最慢的一个点仅用时0.62秒。


六、注意事项

上文介绍的内存分配器缺少对内存上限的处理,因为有时我们并不清楚要开多大的space才够,所以当内存不足时通常要进行额外处理,用allocator来分配内存以防止内存泄露是个不错的选择,请读者自己完善代码。

另外有些C++容器允许通过reserve成员函数预留内存,避免不必要的重复申请操作,这也是一个很有效的优化方法。

浅谈C++容器动态内存管理的优化,布布扣,bubuko.com

时间: 2024-10-25 06:59:52

浅谈C++容器动态内存管理的优化的相关文章

浅谈swift中的内存管理

Swift使用自动引用计数(ARC(Automatic Reference Count))来管理应用程序的内存使用.这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理.当实例并不再被需要时,ARC会自动释放这些实例所使用的内存. 内存管理:针对的是实例的内存占用的管理(放在堆里面) 实例:1:由class类型构建的实例,2:闭包对象 下面我们来写一个实例来证明一下 class Person { var name: String init(name: String )

浅谈大型网站动态应用系统架构【转】

浅谈大型网站动态应用系统架构 动态应用,是相对于网站静态内容而言,是指以c/c++.php.Java.perl..net等服务器端语言开发的网络应用软件,比如论坛.网络相册.交友.BLOG等常见应用.动态应用系统通常与数据库系统.缓存系统.分布式存储系统等密不可分. 大型动态应用系统平台主要是针对于大流量.高并发网站建立的底层系统架构.大型网站的运行需要一个可靠.安全.可扩展.易维护的应用系统平台做为支撑,以保证网站应用的平稳运行. 大型动态应用系统又可分为几个子系统: l l l l l l

动态内存管理

(1).c中动态内存管理方式 malloc.calloc.realloc在堆上开辟空间.free将申请的空间释放掉 void *malloc( size_t size );      void *calloc( size_t num, size_t size );      void *realloc( void *memblock, size_t size ); (2).C++中动态内存管理 通过new和delete运算符进行动态内存管理 (3).malloc/free和new/delete的

iOS开发之c语言基础Lesson-10 动态内存管理 上课笔记 与 试题练习

//函数声明 int max(int a, int b); char * getString(); int main(int argc, const char * argv[]) { //////////////////Lesson 10 动态内存管理 课堂笔记 和 练习 ///////复习上周所学 /////////常量和变量的区别: //常量中的内容不能被修改,只能访问: //变量中存储的数据时可以随时修改的. // //const 关键字的作用: 修饰变量不可改变.当做常量使用 //  c

浅谈软件项目的需求管理

软件项目区别于其它项目的最显著的特征是其不可见性,它不像硬件购销.建筑工程,都是实实在在可见的东西.而软件项目在系统交付之前很长一段时间,客户是无法感知自己想要的系统究竟是什么样子.因此,需求管理就显得十分重要,据相关统计数据分析,软件项目90%以上失败的原因都在于没有重视需求或者需求管理方面做的不到位导致的. 需求管理作为软件项目管理的一个重要内容,贯穿项目实施的全生命周期.俗话说:万事开头难.需求作为软件开发的第一个环节,其重要性不言而喻.市面上关于需求管理的相关理论和书籍很多,但多数停留在

C语言动态内存管理

1-概述 动态存储管理的基本问题是:系统如何按请求分配内存,如何回收内存再利用.提出请求的用户可能是系统的一个作业,也可能是程序中的一个变量. 空闲块 未曾分配的地址连续的内存区称为"空闲块". 占用块 已分配给用户使用的地址连续的内存区称为"占用块". 系统刚刚启动时,整个内存可看做一个大的"空闲块",随着用户请求的进入,系统依次分配相应的内存. 在系统运行过程中,内存被分为两大部分:低地址区(若干占用块)和高地址区(空闲块). 经过一段时间后

c++中的动态内存管理

c++中的动态内存管理问题 c++中使用new和delete实现动态内存管理.new和delete实现动态管理对象,new[]和delete[]实现动态管理对象数组.c++中的new和delete运算符均使用我们c中学过的malloc和delete函数实现动态内存的开辟. 首先,先简单介绍下c中的几个动态内存函数malloc,realloc,calloc,free; void *malloc(size_t size); //动态内存开辟函数 void free(void *pointer);  

动态内存管理(引用计数)

c++的动态内存管理是非常重要的,操作不当很容易引起内存泄漏, 下面我详细写了一些内存管理该注意的地方,包括引用计数的实现 深拷贝浅拷贝 #include <iostream>using namespace std; class String{public: String()  :_str(new char[1]) {  *_str = '\0'; } String(char* str)  :_str(new char[strlen(str)+1])  //开辟一段新空间给_str {  st

动态内存管理---new&amp;delete

动态内存管理 动态对象(堆对象)是程序在运行过程中在动态内存中用new运算符创建的对象. 由于是用户自己用new运算符创建的,因此也要求用户自己用delete运算符释放,即用户必须自己管理动态内存. 计算机内存数据区常有三种分区,即静态数据区.堆区.桟区. 1.程序在编译时就为静态变量和静态对象分配了静态数据存储区.在静态数据区中存储的变量或对象在该程序的整个运行期间都存在, 它们的生命周期贯穿整个程序的运行周期.比如全局变量.static(静态)变量等都是存储在静态数据区. 2.调用函数时,函