编程宝典 保持C/C++程序代码可伸缩性
如果虚拟内存已经被提交,那么你应该检查那些通常包含了最近被释放的堆内存块的内存范围,以确认是否有此范围内的内存被提交,而此范围内任何被提交的内存部分都应该是在一个堆内存范围内,特别是如果它包含了跟踪列表上的内存块,进行此检查可保证进一步的准确性。接下来还有以下两件事,如例2中所示:
·如果被跟踪的堆内存范围内任一部分被提交,必须更新你的跟踪列表。
·否则,删除列表中的元素并跟踪新的范围。
如果提交的部分在中间,那么就有可能把堆内存范围截成好几断,总而言之,在你放弃跟踪老的范围之前,应先在全范围内检查一下哪一部分仍处于提交状态。
程序中可能会用到好几个不同的堆,在Windows上,如果你调用HeapCreate()并把返回的句柄传给接下来的HeapAlloc()、HeapReAlloc()、HeapFree()函数调用时,就会创建一些不同的堆;另外,如果你加载了C运行时库DLL的多个实例,也会因为每个实例使用它们自己的堆而产生多个堆。此时,可在不同的列表中跟踪多个堆,也可在不同的列表中跟踪它们的内存块。首先,这样做的好处是,列表的查找可以变得很快,其次,当堆被"摧毁"时,你可以毫无顾虑地清除跟踪列表,但这需要在堆创建和释放时加入额外的代码来完成--即分别设置和删除对应的列表。另外,你也可管理一个堆内存块的列表及一个堆范围的列表,并在程序开始运行时建立它们。
一些专业的运行时分析工具也能对分配的内存块及堆范围进行跟踪,就像前面所说的自定义内存分配函数与释放函数一样,甚至还能通过基本的虚拟内存HASH值(ox187d690)进一步跟踪,以便为精确的运行时错误检测提供更加可靠的手段。但此处描述的方法并不足以帮助你理解何时才能找到通过程序可控制的堆内存块,减少虚拟内存消耗的时机。
找到适当的清理时机
为使用跟踪信息以精确定位那些导致虚拟内存不必要增长的堆内存块,你还必须要记录下内存访问动作,并在程序读写堆内存时,标记出相应的内存块跟踪结构。如果你的堆内存块都是通过存取函数访问的,那么很容易就可找到所有代码读写的内存部分,并以条件编译生成一个特定的版本。这些存取函数可查找你列表上被访问过的内存块,并设置布尔值作出标记。
当虚拟内存被提交生成一个堆,你的自定义内存分配函数应取消堆中所有内存块的标记,而在接下来,它们可能会重新被标记上,一个接一个,就像你的程序正在访问它们。当一个取消标记的内存块被释放时,相应的虚拟内存也会被释放,此时你自定义的释放函数就会先一步释放内存块,以减小虚拟内存的覆盖范围。
也可安排对堆内存块作一些临时的扫描,这也许可在每一次虚拟内存提交时进行。如果一个内存块在经过多遍扫描后仍保持未标记状态(也许会花很长时间),则包含此内存块的虚拟内存范围就必须对所跟踪的内存块数进行检查。如果那个内存块,或者一组被忽视的类似内存块,只是单独地与提交的虚拟内存有关系,那么通过释放与重新定位这些内存块,你可能已经找到一个减少程序虚拟内存要求的好方法。
如果你实现了此处描述的所有内存块和堆范围跟踪代码,而当这些所有的跟踪都起作用时,那么程序的速度将会变得很慢,主要是因为在每一次堆内存访问时,都会进行一遍列表查找,当然,也可以通过一些快速列表查找方法如二分法查找、跳跃查找之类的来缩短查找的时间,还可使用对应每个堆的单独列表来加速查找。如果程序使用了许多的堆内存块,并且也找到了减少额外虚拟内存消耗的方法,以往所花费的所有精力与耐心,与此时得到的回报相比,就算不上什么了。
Tags:
作者:佚名评论内容只代表网友观点,与本站立场无关!
评论摘要(共 0 条,得分 0 分,平均 0 分)
查看完整评论