日期:2011-03-22 13:55:00 来源:本站整理
保持C/C++程序代码的可伸缩性[VC/C++编程]
本文“保持C/C++程序代码的可伸缩性[VC/C++编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
在本日,已有很多的32位利用程序感到,在32位平台上可用的虚拟内存遭到了一定的限制,对程序开辟者来说,即便是开始关注64位平台时,也不得不保护软件的32位版本,这就需求一种办法,以使代码的两个版本都保持相当的可伸缩性.
目前的内存解析工具能帮忙肯定,当程序到达峰值内存利用量时,都发生了什么,但是这些工具都过于关注已分配的内存块,而不是已提交的虚拟内存地址空间,而这两种衡量尺度没有直接的相关性,如内存泄露、内存碎片、内存块内的空间浪费、或过度耽误的内存单元重新分配这些因素,城市招致不必要的虚拟内存提交.运行时解析工具如IBM Rational Purify或Parasoft Inuse都可以供应内存泄露及已用内存的描写,这些信息是非常有效的,但是,一个特别的内存块大概大概、大概不大概影响到虚拟内存覆盖区,别的,乃至一个有碎片的内存堆中的一个小块,也能直接影响到虚拟内存覆盖区.从另一方面来说,在此范围内的肆意内存块--乃至泄露的块,对虚拟内存覆盖区来说,也不会与之有什么关系,除非每一个此范围内有效的内存块能重新分配到一个更紧凑的范围内,这就有点像Java或托管程序的垃圾回收机制,但对大大都C/C++本地利用程序来说,就绝对不大概了,因为在虚拟内存空间中,它们内存块的位置是不肯定的.
至于本地代码,不必要的虚拟内存利用,这个实际的问题,比未清理的内存块这个理论上的问题,越发有实质性.未清理的内存块大概招致虚拟内存的浪费,造成过量的系统开销,但大概不会;这完好依靠于堆管理器能否提交了更多的虚拟内存,以支持这种浪费.某些很小的未利用的内存块,不会惹起不必要的堆"扩大".与其让你来猜哪一个或多少已浪费的内存块招致了堆扩大,倒不如学会怎样断定出有意义的浪费是什么.当堆中包含不再利用的内存块时,此时通过加入对未缩减堆的查抄,就可以肯定出与你的程序虚拟内存要求有很大关系的、必须举行的内存块清理.
为找出哪一个堆中的内存块需多留神,必须在程序中加入一些额外的代码,以跟踪内存堆范围及已分配的内存块.对额外的代码举行条件编译,生成一个特定的版本,大概是一个不错的办法.
为到达此目的,需编写自定义的内存分配例程,并跟踪每一个内存块,还有一个自定义的释放例程,且跟踪虚拟内存中堆的位置,请拜见例1与例2的伪代码算法.大概还需编写自定义的拜候函数以标志出拜候过的内存块,以便于在得当的时刻释放虚拟内存,全部这些并不需求过量的内存开销.另一方面,假如你的程序以堆的情势利用了大量的内存,那么将会极大地降低性能,此处的办法也不是长期之计.
例1:
/* 输入参数*/ ADDRESS triggerAddr SIZE triggerSize LIST a list of tracked heap ranges IF (the virtual memory at triggerAddr is tracked on the list as part of a heap range) DO IF (triggerAddr + triggerSize > (the tracked upper boundary of that heap range)) DO /* 一个现有的堆范围被扩大 */ make system call(s) to determine the base and extent of the newly committed range that contains the addresses from triggerAddr to (triggerAddr + triggerSize) update the size of the tracked heap range to indicate its new upper limit END END ELSE DO /* 在triggerAddr中有一个新的堆范围 */ make system call(s) to determine the base and extent of the newly committed range that contains the addresses from triggerAddr to (triggerAddr + triggerSize) track the new committed range in the list of heap ranges END |
例2:
/* 输入参数 */ ADDRESS triggerAddr SIZE triggerSize LIST a list of tracked heap ranges /* 部分变量 */ ADDRESS origRangeBase SIZE origRangeSize BOOL bFoundChange bFoundChange = FALSE IF (the virtual memory at triggerAddr is not tracked on the heap range list as part of a heap range) DO /*仿佛我们已经清楚此次释放了.*/ END ELSE IF (an access exception occurs when memory at triggerAddr is read) DO bFoundChange = TRUE END IF (bFoundChange) DO /*因为之前内存块占用的空间被释放了,所以堆占用的虚拟内存范围就改变了.*/ make system calls to determine the bases and extents of the tracked committed heap ranges in the immediate vicinity of the decommitted range that includes the addresses from triggerAddr to (triggerAddr + triggerSize) /*更新堆范围跟踪,以反映剩余提交的范围 */ IF (any portion of the tracked heap range that contained the block at TriggerAddr is still committed) DO update the heap range list to track just the portion(s) of that range that remain committed END ELSE DO delete the list element that tracks the range END END |
跟踪堆内存块
可以利用自定义的内存分配函数来举行内存块的跟踪,而这种函数最初被称为普通内存分配函数,举例来说,C语言程序中普通利用malloc(),此后,自定义的内存分配会举行以下一系列的操作:
·在目前已分配的内存块列表中,跟踪新分配的内存块.
·决意能否向系统提交虚拟内存.
·假如虚拟内存已被提交,跟踪包含此内存块的堆范围,并更新上述堆内存块列表,以标识出从未被拜候过的内存块.
还需求自定义的释放与重分配内存函数,以便通历程序中利用的内存块的地址与大小,来更新内存块列表.所跟踪的堆内存块列表应包含以下构造:
·内存块的基地址.
·自身大小
·用于指导自从上次虚拟内存被提交之后,内存块能否被拜候过的布尔值.
当一个内存块被释放后,自定义的释放代码将会举行以下操作:
·假如自从上次堆扩大之后,内存块还未被拜候过,将向程序报告.
·从列表中删除跟踪的内存块.
·断定系统能否已释放了包含此内存块的虚拟内存.
·假如虚拟内存被释放,要呼应地更新,以反映剩余的堆范围.
当一个内存块被重新分配时,你的自定义重新分配内存代码必须举行以下两种操作:首先,在释放之后重新跟踪内存块,因为重新分配的内存块大概不在原位置;其次,在分配之后也要重新跟踪新的基地址及分配内存块的大小.还有一个可选的办法,你可查抄能否重新分配的内存块被移动了,假如没有被移动,只需仅仅更新内存块跟踪列表,标出此内存块的大小;假如内存块还在同一基地址,但是增长了,此时就要查抄堆扩大,并按照前述分配内存的办法重来一遍.
跟踪堆自身
堆跟踪取决于当内存块被分配或释放时,虚拟内存能否辨别被提交或释放,依此可以成立一张堆内存范围跟踪表,以肯定在程序运行期间,虚拟内存空间中堆的切当位置,跟踪列表中应包含以下数据:
·跟踪范围的基地址
·自身大小
在Windows操作系统中,这些值可通过HeapWalk()调用得到,此处要注意的是,HeapWalk()函数调用开销宏大,因此,只在程序需求时调用,而不是当有内存分配或释放时都调用.另一种Windows上的办法是利用IsBadReadPointer()函数,当一个内存块被释放后,你可以调用这个函数快速地断定包含此内存块的虚拟内存能否已被释放.另一个可以跨平台的备选办法是,可试着拜候包含此内存块的虚拟内存,并捕捉大概发生的拜候非常.此外,只有在一种情形下会考虑利用如HeapWalk()这样开销宏大的函数,就是需断定临近剩余的已提交堆范围.
通过一种探测堆内存提交的算法,堆内存跟踪列表会不断地增长,如例1中所示.要注意的是,当你的程序分配一个内存块时,自定义的内存分配代码也能跟踪到这些内存块,并利用例1中的算法来更新包含内存块的堆列表.假如一个内存块的分配招致了额外的虚拟内存被提交,那么被内存块占用的虚拟内存会在之前就释放,大概之前就被用作别的用处.在任一情形中,必须有条件地更新堆范围跟踪列表: l 假如正在跟踪已提交范围的基地址,此时必须更新范围的大小,以指导出新的范围上限.
·不然,必须成立一张新的堆内存范围跟踪表.
假如一个新的内存块呈目前一个之前未被跟踪的堆范围中,就满意了以上条件,此时明智地利用前述的系统调用可高效地跟踪堆内存范围.
当你的程序释放一个内存块时,自定义的内存释放代码会利用到如例2中的算法,此算法会先断定释放的内存能否与被跟踪的堆内存范围有关;接下来,必须查抄已释放内存块占用的空间能否仍处于提交状况,假如是,表明了即便内存块被释放,虚拟内存的覆盖区也没有发生改变,不然,你的代码必须举行如例2末尾处的系统调用--如Windows中的HeapWalk()--以确保跟踪的堆是最新的,且包含堆内存块的虚拟内存已被释放.
假如虚拟内存已经被提交,那么你应当查抄那些普通包含了近来被释放的堆内存块的内存范围,以确认能否有此范围内的内存被提交,而此范围内任何被提交的内存部份都应当是在一个堆内存范围内,分外是假如它包含了跟踪列表上的内存块,举行此查抄可保证进一步的精确性.接下来还有以下两件事,如例2中所示:
·假如被跟踪的堆内存范围内任一部份被提交,必须更新你的跟踪列表.
·不然,删除列表中的元素并跟踪新的范围.
假如提交的部份在中间,那么就有大概把堆内存范围截成好几断,总而言之,在你放弃跟踪老的范围之前,应先在全范围内查抄一下哪一部份仍处于提交状况.
程序中大概会用到好几个差别的堆,在Windows上,假如你调用HeapCreate()并把返回的句柄传给接下来的HeapAlloc()、HeapReAlloc()、HeapFree()函数调用时,就会成立一些差别的堆;别的,假如你加载了C运行时库DLL的多个实例,也会因为每个实例利用它们自己的堆而产生多个堆.此时,可在差别的列表中跟踪多个堆,也可在差别的列表中跟踪它们的内存块.首先,这样做的好处是,列表的查找可以变得很快,其次,当堆被"摧毁"时,你可以毫无顾忌地排除跟踪列表,但这需求在堆成立和释放时加入额外的代码来完成--即辨别设置和删除对应的列表.别的,你也可管理一个堆内存块的列表及一个堆范围的列表,并在程序开始运行时成立它们.
一些专业的运行时解析工具也能对分配的内存块及堆范围举行跟踪,就像前面所说的自定义内存分配函数与释放函数一样,乃至还能通过基本的虚拟内存HASH值(ox187d690)进一步跟踪,以便为切确的运行时错误检测供应越发坚固的手段.但此处描写的办法并不足以帮忙你理解什么时刻才能找到通历程序可掌握的堆内存块,削减虚拟内存损耗的机会.
找到得当的清理机会
为利用跟踪信息以切肯定位那些招致虚拟内存不必要增长的堆内存块,你还必必要记录下内存拜候行动,并在程序读写堆内存时,标志出呼应的内存块跟踪构造.假如你的堆内存块都是通过存取函数拜候的,那么很简单便可找到全部代码读写的内存部份,并以条件编译生成一个特定的版本.这些存取函数可查找你列表上被拜候过的内存块,并设置布尔值作出标志.
当虚拟内存被提交生成一个堆,你的自定义内存分配函数应撤消堆中全部内存块的标志,而在接下来,它们大概会重新被标志上,一个接一个,就像你的程序正在拜候它们.当一个撤消标志的内存块被释放时,呼应的虚拟内存也会被释放,此时你自定义的释放函数就会先一步释放内存块,以减小虚拟内存的覆盖范围.
也可安置对堆内存块作一些暂时的扫描,这也答应在每一次虚拟内存提交时举行.假如一个内存块在经过量遍扫描后仍保持未标志状况(大概会花很长时间),则包含此内存块的虚拟内存范围就必须对所跟踪的内存块数举行查抄.假如那个内存块,大概一组被轻忽的近似内存块,只是单独地与提交的虚拟内存有关系,那么通过释放与重新定位这些内存块,你大概已经找到一个削减程序虚拟内存要求的好办法.
假如你实现了此处描写的全部内存块和堆范围跟踪代码,而当这些全部的跟踪都起作用时,那么程序的速度将会变得很慢,主如果因为在每一次堆内存拜候时,城市举行一遍列表查找,当然,也可以通过一些快速列表查找办法如二分法查找、腾跃查找之类的来缩短查找的时间,还可以利用对应每个堆的单独列表来加快查找.假如程序利用了很多的堆内存块,并且也找到了削减额外虚拟内存损耗的办法,以往所耗费的全部精神与耐烦,与此时得到的回报相比,就算不上什么了.
以上是“保持C/C++程序代码的可伸缩性[VC/C++编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
本文地址: | 与您的QQ/BBS好友分享! |
评论内容只代表网友观点,与本站立场无关!
评论摘要(共 0 条,得分 0 分,平均 0 分)
查看完整评论