Chinaunix首页 | 论坛 | 博客
  • 博客访问: 91628
  • 博文数量: 23
  • 博客积分: 870
  • 博客等级: 准尉
  • 技术积分: 210
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-02 16:52
文章存档

2011年(1)

2010年(1)

2009年(21)

我的朋友

分类: 嵌入式

2009-09-04 21:53:49

heap(堆)和stack(栈)的区别:


可简单理解为:没有堆你就不能给程序分配内存;没有栈你就不能调用函数
stack是把推进栈里的元素按照先进后出的顺序逐一的弹出,而heap是允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先级最高的元素开始取.有max-heap和min-heap两种.max-heap首先取出的是推入容器中的最大的元素,min-heap首先取出的是推入容器中的最小的元素.
stack是局部的;heap是全局的  
你在程序里new或alloc一个对象就是从heap分配的;而你调用函数时的参数是利用stack来存储的(压入栈)
下面是heap(堆)和stack(栈)几点概括:
1、函数里的局部变量是在stack里的.  
  2、全局变量,用new、malloc等分配的内存是在heap里的.  
  3、什么时候用heap,什么时候用stack要看你所使用的内存生存期.  
  4、如果使用的变量只在函数里有用,建议用stack.因为heap很慢  
  5、如果使用很大块的内存,建议用heap.因为stack可能会溢出.  
  6、如果在函数里面分配的内存,还想在函数返回之后使用,要用new、malloc,或者定义成静态变量,静态变量是在heap里的.  
  7、……


                                什么是堆?    


    
  在程序中,使用堆来动态分配和释放对象.在下列情况下,调用堆操作:    
    
  事先不知道程序所需对象的数量和大小.    
  对象太大而不适合堆栈分配程序.    
    
  堆使用了在运行时分配给代码和堆栈的内存之外的部分内存.下图给出了堆分配程序的不同层.    
  http://www.microsoft.com/china/msdn/library/images/heap3.gif    
  GlobalAlloc/GlobalFree:Microsoft   Win32   堆调用,这些调用直接与每个进程的默认堆进行对话.    
  LocalAlloc/LocalFree:Win32   堆调用(为了与   Microsoft   Windows   NT   兼容),这些调用直接与每个进程的默认堆进行对话.    
  COM   的   IMalloc   分配程序(或   CoTaskMemAlloc   /   CoTaskMemFree):函数使用每个进程的默认堆.自动化程序使用“组件对象模型   (COM)”的分配程序,而申请的程序使用每个进程堆.    
    
  C/C++   运行时   (CRT)   分配程序:提供了   malloc()   和   free()   以及   new   和    
  delete   操作符.如   Microsoft   Visual   Basic   和   Java   等语言也提供了新的操作符并使用垃圾收集来代替堆.CRT   创建自己的私有堆,驻留在   Win32   堆的顶部.    
    
  Windows   NT   中,Win32   堆是   Windows   NT   运行时分配程序周围的薄层.所有    
  API   转发它们的请求给   NTDLL.    
  Windows   NT   运行时分配程序提供   Windows   NT   内的核心堆分配程序.它由具有128   个大小从   8   到   1,024   字节的空闲列表的前端分配程序组成.后端分配程序使用虚拟内存来保留和提交页.    
  在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页.所有分配程序使用虚拟内存进行数据的存取.    
  分配和释放块不就那么简单吗?为何花费这么长时间?    
  堆实现的注意事项    
  传统上,操作系统和运行时库是与堆的实现共存的.在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”.如果没有其他堆可使用,则块的分配使用“进程堆”.语言运行时也能在进程内创建单独的堆.(例如,C   运行时创建它自己的堆.)除这些专用的堆外,应用程序或许多已载入的动态链接库   (DLL)   之一可以创建和使用单独的堆.Win32   提供一整套   API   来创建和使用私有堆.有关堆函数(英文)的详尽指导,请参见   MSDN.    
  当应用程序或   DLL   创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的.从给定堆分配的数据将在同一个堆上释放.(不能从一个堆分配而在另一个堆释放.)    
  在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部.语言运行时堆也驻留在虚拟内存顶部.某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理.不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用.    
  典型的堆实现由前、后端分配程序组成.前端分配程序维持固定大小块的空闲列表.对于一次分配调用,堆尝试从前端列表找到一个自由块.如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求.通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间.


什么是常见的堆性能问题?    
    
  以下是您使用堆时会遇到的最常见问题:    
  分配操作造成的速度减慢.光分配就耗费很长时间.最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块.    
    
  释放操作造成的速度减慢.释放操作耗费较多周期,主要是启用了收集操作.收集期间,每个释放操作“查找”它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表.在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低.    
  堆竞争造成的速度减慢.当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争.竞争总是导致麻烦;这也是目前多处理器系统遇到的最大问题.当大量使用内存块的应用程序或   DLL   以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢.单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的.当等待锁定时序列化会引起线程切换上下文.可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢.    
    
  竞争通常会导致线程和进程的上下文切换.上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建.    
  堆破坏造成的速度减慢.造成堆破坏的原因是应用程序对堆块的不正确使用.通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题.    
    
  (破坏不在本文讨论范围之内.有关内存重写和泄漏等其他细节,请参见    
  Microsoft   Visual   C++(R)   调试文档   .)    
    
  频繁的分配和重分配造成的速度减慢.这是使用脚本语言时非常普遍的现象.如字符串被反复分配,随重分配增长和释放.不要这样做,如果可能,尽量分配大字符串和使用缓冲区.另一种方法就是尽量少用连接操作.    
    
  竞争是在分配和释放操作中导致速度减慢的问题.理想情况下,希望使用没有竞争和快速分配/释放的堆.可惜,现在还没有这样的通用堆,也许将来会有.    
    
  在所有的服务器系统中(如   IIS、MSProxy、DatabaseStacks、网络服务器、    
  Exchange   和其他),   堆锁定实在是个大瓶颈.处理器数越多,竞争就越会恶化.    
    
  尽量减少堆的使用    
    
  现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有.但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观.如果提前规划堆策略,情况将会大大好转.调整使用堆的方法,减少对堆的操作是提高性能的良方.    
  如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数.请考虑下列实例:    
  struct   ObjectA   {    
    
        //   objectA   的数据    
    
  }    
    
  struct   ObjectB   {    
    
        //   objectB   的数据    
    
  }    
    
  //   同时使用   objectA   和   objectB    
  //    
    
  //   使用指针    
  //    
  struct   ObjectB   {    
    
        struct   ObjectA   *   pObjA;    
    
        //   objectB   的数据    
    
  }    
    
  //    
  //   使用嵌入    
  //    
  struct   ObjectB   {    
        struct   ObjectA   pObjA;    
        //   objectB   的数据    
  }    
    
        
  //    
  //   集合   –   在另一对象内使用   objectA   和   objectB    
  //    
  struct   ObjectX   {    
        struct   ObjectA     objA;    
        struct   ObjectB     objB;    
  }    
    
        
  避免使用指针关联两个数据结构.如果使用指针关联两个数据结构,前面实例中的对象   A   和   B   将被分别分配和释放.这会增加额外开销—我们要避免这种做法.    
    
  把带指针的子对象嵌入父对象.当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置.嵌入增加了位置从而减少了进一步分配/释放的需求.这将提高应用程序的性能.    
  合并小对象形成大对象(聚合).聚合减少分配和释放的块的数量.如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并.集成的挑战就是要找到正确的聚合边界.    
  内联缓冲区能够满足百分之八十的需要(aka   80-20   规则).个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数.估计并内联一个大小能满足百分之八十需要的缓冲区.对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针.这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能.    
  在块中分配对象(块化).块化是以组的方式一次分配多个对象的方法.如果对列表的项连续跟踪,例如对一个   {名称,值}   对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构.例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针.    
    
  块化是友好的处理器高速缓存,特别是对于   L1-高速缓存,因为它提供了增加的位置   —不用说对于块分配,很多数据块会在同一个虚拟页中.    
  正确使用   _amblksiz.C   运行时   (CRT)   有它的自定义前端分配程序,该分配程序从后端(Win32   堆)分配大小为   _amblksiz   的块.将   _amblksiz   设置为较高的值能潜在地减少对后端的调用次数.这只对广泛使用   CRT   的程序适用.    
    
  使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同.但总能在性能和可升缩性方面有所收获.另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的.
阅读(1208) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~