锁定老帖子 主题:项目事故和安全语言
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-10-10
potian 写道 实际上复杂的内存分配策略并一定能够减少内存分配碎片,譬如论文中提到很多情况下first fit比best fit更加好,所以也不意味着产生内存碎片越少的算法复杂度越高。
缩并的算法虽然可以直接通过指针移动快速分配内存,而且看起来局部性更好,但是相邻内存空间生命周期的近视性使得局部性的意义不大,而缩并操作时造成缺页的可能性更大,因为需要缩并、移动,而非缩并可能只需要操作bitmap即可。另外需要半空间也是个大问题。另外,缩并本身也是需要成本的。应该说,缩并算法从最早的压倒性优势到目前很多新的GC没有采用的趋势还是比较明显的,尽管复杂的GC可能会进行适应性的算法。 当然,这些都和程序的具体内存行为有关。 确实,复杂的内存分配策略并不一定能够减少内存分配碎片,但这与我的观点并不矛盾:不能因为某些内存分配机制产生的内存碎片数量极少就得出问题已经解决的结论,还是应该权衡考虑内存分配的耗时等其他因素。 关于缩并的垃圾收集机制,你的说法让我有点迷惑,似乎应该澄清一下概念。 能够实现“缩并”效果的垃圾收集算法有两类,一类是在标记-清扫算法基础上加上缩并阶段,另一类是基于节点复制的算法。你所说的缩并指的是哪一种?前一类算法有额外的缩并阶段,所以“缩并本身也是需要成本的”,但不需要半空间;后一类算法“需要半空间”,但是缩并效果是收集过程的自然结果,不需要额外的成本。你两条都提到了,让我有些迷惑。另外,你说“相邻内存空间生命周期的近似性使得局部性的意义不大”,不是很清楚你这里的所指,可以解释一下么? 不过对于带有缩并效果的算法执行过程本身可能造成缺页这一点,我完全赞同,并且认为这可能是这一大类算法最大的问题。 |
|
返回顶楼 | |
发表时间:2006-10-10
确实说得思路不太清楚,我写的时候把想到的不同的收集方式的缩并相关所有问题都堆上来了。是否缩并和节点复制/标记是正交的问题。节点复制式(需要半空间)只不过具有自然的缩并效果,它需要的是复制的开销,当然可以说不是额外的开销。另外的还有标记缩并算法。标记缩并通常不需要清扫的,而是标记+缩并,不是标记清扫+缩并。
除了复制自然缩并外,缩并算法可以还采用诸如双指针、迁移地址、表方法和穿线法等等。这些缩并算法一般需要遍历存活数据结构多次。而且任意型的缩并算法(如双指针)本身在缩并的时候并没有考虑局部性。线性的方法(如节点复制的)从引用关系考虑局部性。滑动方法从分配位置考虑局部性。不能说缩并局部性一定就好了。 关于我说的生命周期的相似性,就是说同时创建的对象通常来说是同时存活或同时死亡的,并且它们极大的可能占据相近的空间,因此,要么局部性很好,要么一下子都被收集了。不大可能造成程序的工作集分布在不同的页面上,页面频繁换进换出。这方面文献应该也不会太少。 |
|
返回顶楼 | |
发表时间:2006-10-10
potian 写道 确实说得思路不太清楚,我写的时候把想到的不同的收集方式的缩并相关所有问题都堆上来了。是否缩并和节点复制/标记是正交的问题。节点复制式(需要半空间)只不过具有自然的缩并效果,它需要的是复制的开销,当然可以说不是额外的开销。另外的还有标记缩并算法。标记缩并通常不需要清扫的,而是标记+缩并,不是标记清扫+缩并。
除了复制自然缩并外,缩并算法可以还采用诸如双指针、迁移地址、表方法和穿线法等等。这些缩并算法一般需要遍历存活数据结构多次。而且任意型的缩并算法(如双指针)本身在缩并的时候并没有考虑局部性。线性的方法(如节点复制的)从引用关系考虑局部性。滑动方法从分配位置考虑局部性。不能说缩并局部性一定就好了。 关于我说的生命周期的相似性,就是说同时创建的对象通常来说是同时存活或同时死亡的,并且它们极大的可能占据相近的空间,因此,要么局部性很好,要么一下子都被收集了。不大可能造成程序的工作集分布在不同的页面上,页面频繁换进换出。这方面文献应该也不会太少。 你这样说就清楚多了。 确实缩并的算法不一定有优势,它们最大的问题是无论如何都要移动对象,而且多半会触及被收集的堆中大部分甚至所有的对象。因此这类算法在执行收集过程中要访问堆的不同部分,这往往导致更多的缺页错误。但另一方面,这类算法在内存分配效率的优势也的确不容忽视。局部性方面么,要看具体实现,简单地缩并算法(比如你提到的双指针算法)可能反而恶化了程序的局部性,但也有充分考虑内存访问局部性乃至 cache 命中的实现,甚至存在一些实现是有“训练”过程的,这个就又很难说 …… 总之,我仍然认为,对于这么复杂而且不确定的课题,是很难下一个明确的结论的 ………… |
|
返回顶楼 | |
发表时间:2006-10-19
这纯属态度问题,安全的语言难道就不能谨慎了么?
难道就不写单元测试了? 不能因噎废食啊 |
|
返回顶楼 | |
发表时间:2006-10-24
Elminster对于GC仍然那么感兴趣。其实就memory来说,其分配和回收策略确实千变万化,考虑不同程序的不同局部性和临时性为的时候更是如此。从一个程序内部考虑其cache missing rate其实是不够准确的,必定得涉及到别的进程的行为,其实,memory这个资源的使用方式,最合理明智的决定只能是综合参考了进程和OS双方的信息之后才能给出的。Activation Scheduler对于CPU这个资源作的事情,也可以进一步推广到memory上。
|
|
返回顶楼 | |
发表时间:2007-06-28
bigpanda 写道 fustic 写道 什么时候会遇到内存碎片?
内存碎片和硬盘碎片原理差不多。 举个例子,程序刚开始运行的时候,连续五次向Heap申请内存,每次1k,然后释放了第二个,第四个,然后再申请4k内存,这4k内存不是连续的,而是分成了三片,在原来的第二块,第四块和第五块后面。这样在这4k里面读写数据就会在内存里跳来跳去,影响性能。 上面这段有错误。 举个例子,程序刚开始运行的时候,连续五次向Heap申请内存,每次1k,然后释放了第二个,第四个,然后再申请4k内存,这4k内存只能放在原来的第五块后面。如果将来申请的内存都大于1k,中间的第二块,第四块就成碎片。 http://en.wikipedia.org/wiki/Fragmentation_%28computer%29 推荐几本书, Programming Applications for Microsoft Windows, 4th Edition, by Jeffrey Richter 这本书对Win32 API讲的非常好,process, thread, job, virtual memory, heap, thread stack都讲的很清楚,概括了操作系统的很大一部分,结合API讲,比较实际,比光讲理论的操作系统教科书好理解。操作系统教科书里面谈到Windows NT/2k/XP都推荐这本书。 Memory Management: Algorithms and Implementations In C/C++,Bill Blunden,结合处理器讲,有些东西别的书里都没写,比如操作系统如何实现Atomic操作,semaphore是怎么实现的。 IA-32 Intel® Architecture Software Developer's Manual Volume 3A: System Programming Guide, Part 1,可下载: http://www.intel.com/design/Pentium4/manuals/253668.htm 操作系统是和处理器紧密结合的,唇齿相依,把这本书翻一遍可以大大加深理解,要能精通就是大牛了,可以在Linux,BSD内核里折腾了。 原理我也知道,但是从来没试过因为内存碎片导致应用异常的情况(也许是遇到了也不知道是怎么回事,呵呵) 一般写7*24程序时,会尽量避免使用heap,而使用栈空间,或者一次分配足够的内存(也许嵌入式系统不能这样干) 另外,据说stl有自己的内存管理系统,不知道是否会做一些内存合并的功能,或者使用一些非glibc的malloc替代品,有很多高性能的内存分配库,比如google的,或者其他的,都会有很强的内存管理能力。 不要太依靠操作系统,它是面向通用程序的,不会为专门的程序做优化,呵呵 |
|
返回顶楼 | |
发表时间:2007-06-28
ddd 写道 》虚拟内存是连续的,未必物理内存也是连续的。
如何保证物理内存是连续的? linux内核里面有专门的api,可以申请连续的物理内存,一般好像是给DMA使用的, 很久之前看过,如果有兴趣,不妨翻一下<understanding linux kernel>,还是比较 通俗易懂的 |
|
返回顶楼 | |
发表时间:2007-06-29
看来大家对硬件和操作系统有不少误解啊。
以下说明仅仅针对x86CPU和32位操作系统。X64及64位操作系统我也不清楚,不过差别不会太大。 1. X86CPU一旦进入保护模式,就不会直接访问物理内存了。 必须在分段,分页或者段页式中选择一种。因此内核中的地址也是虚拟的。要经过GDT/LDT/TLB转换,才能得到物理内存的地址。 2. 内存和内存地址是2个概念,都是很重要的资源。对于内核来说,有4GB的内存地址和物理内存数目的物理内存。对于用户态程序来说,有2-3GB的内存地址(因OS而异)。物理内存数不确定,由操作系统动态分配。 当程序向OS要求分配内存时,仅仅得到了内存地址。而不是物理内存的。在真正使用内存时,才会该地址才会被映射到物理内存,或者是交换文件。 3.为什么说操作系统可以在进程结束时回收所有的内存。因为OS有所有的虚拟地址到物理内存的映射关系。所以很容易回收所有的物理内存。一个成熟的OS这方面是不会有BUG。但是有很多资源是OS无法自动回收的(或者说很难)。比如进程间通讯用的共享内存,内核态的同步对象,内核态的时钟等等。应为这些都可以被多个进程共享,撤销一个进程时很难判断是否能被回收。 4. 关于内存碎片。内存碎片本质上是内存地址不足造成的。举个极限例子,假设进程需要256M内存,然后把这部分内存按照每16K使用4K的方式排列,那么就无法分配到12K以上的内存了。不是应为没有物理内存,而是没有连续的内存空间了。因此对于64位系统,应为相对于物理内存而言有极大的地址资源,基本上不用担心内存碎片的问题了。 5. C++的memory leak。综上所述,C++的memory leak最主要消耗的是内存地址资源,而不是物理内存。因为被泄漏的内存不会再被访问,因此OS会把它们交换到SWAP里去。进程结束时,地址空间当然会被全部回收了。而大部分物理内存在此之前早就被OS回收了。 |
|
返回顶楼 | |
发表时间:2007-07-01
netpcc 写道 看来大家对硬件和操作系统有不少误解啊。
以下说明仅仅针对x86CPU和32位操作系统。X64及64位操作系统我也不清楚,不过差别不会太大。 1. X86CPU一旦进入保护模式,就不会直接访问物理内存了。 必须在分段,分页或者段页式中选择一种。因此内核中的地址也是虚拟的。要经过GDT/LDT/TLB转换,才能得到物理内存的地址。 你说的这个物理、虚拟的概念跟大家讨论的有些出入, 一般我们认为内核逻辑地址就是物理地址,它跟物理地址之间 有一个简单的映射关系 用户级程序看到的地址肯定是虚拟地址,即使它看起来是连续的, 实际在内核里面也是以链表形式组织的 netpcc 写道 2. 内存和内存地址是2个概念,都是很重要的资源。对于内核来说,有4GB的内存地址和物理内存数目的物理内存。对于用户态程序来说,有2-3GB的内存地址(因OS而异)。物理内存数不确定,由操作系统动态分配。 当程序向OS要求分配内存时,仅仅得到了内存地址。而不是物理内存的。在真正使用内存时,才会该地址才会被映射到物理内存,或者是交换文件。 linux 32bit x86cpu 下面,一般是 1:3,用户级内存寻址空间为3G,内核为1G,虽然每个用户级程序 都会看到3G的可用空间,但是并不等于这些data都在内存中,有些可能被OS swap到交换区,但这些细节是对 程序隐藏的。 内核级所看到的1G内存一般是唯一映射到系统物理地址上的,只是为了开发方便,有时内核里面也会把一些不连续的物理内存映射到一个虚拟地址上。 netpcc 写道 3.为什么说操作系统可以在进程结束时回收所有的内存。因为OS有所有的虚拟地址到物理内存的映射关系。所以很容易回收所有的物理内存。一个成熟的OS这方面是不会有BUG。但是有很多资源是OS无法自动回收的(或者说很难)。比如进程间通讯用的共享内存,内核态的同步对象,内核态的时钟等等。应为这些都可以被多个进程共享,撤销一个进程时很难判断是否能被回收。 4. 关于内存碎片。内存碎片本质上是内存地址不足造成的。举个极限例子,假设进程需要256M内存,然后把这部分内存按照每16K使用4K的方式排列,那么就无法分配到12K以上的内存了。不是应为没有物理内存,而是没有连续的内存空间了。因此对于64位系统,应为相对于物理内存而言有极大的地址资源,基本上不用担心内存碎片的问题了。 5. C++的memory leak。综上所述,C++的memory leak最主要消耗的是内存地址资源,而不是物理内存。因为被泄漏的内存不会再被访问,因此OS会把它们交换到SWAP里去。进程结束时,地址空间当然会被全部回收了。而大部分物理内存在此之前早就被OS回收了。 内存泄漏、内存溢出一般都是应用层(用户级空间)的概念,操作系统会维护每个进程的资源(内存、文件句柄等),所以进程一旦退出,所有消耗的资源都会回收 内存碎片则在内核和用户级空间都会存在。 |
|
返回顶楼 | |
发表时间:2007-07-02
fustic 写道 netpcc 写道 看来大家对硬件和操作系统有不少误解啊。
以下说明仅仅针对x86CPU和32位操作系统。X64及64位操作系统我也不清楚,不过差别不会太大。 1. X86CPU一旦进入保护模式,就不会直接访问物理内存了。 必须在分段,分页或者段页式中选择一种。因此内核中的地址也是虚拟的。要经过GDT/LDT/TLB转换,才能得到物理内存的地址。 你说的这个物理、虚拟的概念跟大家讨论的有些出入, 一般我们认为内核逻辑地址就是物理地址,它跟物理地址之间 有一个简单的映射关系 用户级程序看到的地址肯定是虚拟地址,即使它看起来是连续的, 实际在内核里面也是以链表形式组织的 内核中除了页面管理这一小部分,其他模块使用的内存,同样是按照分页的方式来管理的。和应用程序有什么区别? fustic 写道 netpcc 写道 2. 内存和内存地址是2个概念,都是很重要的资源。对于内核来说,有4GB的内存地址和物理内存数目的物理内存。对于用户态程序来说,有2-3GB的内存地址(因OS而异)。物理内存数不确定,由操作系统动态分配。 当程序向OS要求分配内存时,仅仅得到了内存地址。而不是物理内存的。在真正使用内存时,才会该地址才会被映射到物理内存,或者是交换文件。 linux 32bit x86cpu 下面,一般是 1:3,用户级内存寻址空间为3G,内核为1G,虽然每个用户级程序 都会看到3G的可用空间,但是并不等于这些data都在内存中,有些可能被OS swap到交换区,但这些细节是对 程序隐藏的。 内核级所看到的1G内存一般是唯一映射到系统物理地址上的,只是为了开发方便,有时内核里面也会把一些不连续的物理内存映射到一个虚拟地址上。 最上端的1G内存,并不是给内核用的。通常是映射到共享模块和系统API。一旦进入Ring0就使用内核自己的地址空间了。 fustic 写道 netpcc 写道 3.为什么说操作系统可以在进程结束时回收所有的内存。因为OS有所有的虚拟地址到物理内存的映射关系。所以很容易回收所有的物理内存。一个成熟的OS这方面是不会有BUG。但是有很多资源是OS无法自动回收的(或者说很难)。比如进程间通讯用的共享内存,内核态的同步对象,内核态的时钟等等。应为这些都可以被多个进程共享,撤销一个进程时很难判断是否能被回收。 4. 关于内存碎片。内存碎片本质上是内存地址不足造成的。举个极限例子,假设进程需要256M内存,然后把这部分内存按照每16K使用4K的方式排列,那么就无法分配到12K以上的内存了。不是应为没有物理内存,而是没有连续的内存空间了。因此对于64位系统,应为相对于物理内存而言有极大的地址资源,基本上不用担心内存碎片的问题了。 5. C++的memory leak。综上所述,C++的memory leak最主要消耗的是内存地址资源,而不是物理内存。因为被泄漏的内存不会再被访问,因此OS会把它们交换到SWAP里去。进程结束时,地址空间当然会被全部回收了。而大部分物理内存在此之前早就被OS回收了。 内存泄漏、内存溢出一般都是应用层(用户级空间)的概念,操作系统会维护每个进程的资源(内存、文件句柄等),所以进程一旦退出,所有消耗的资源都会回收 内存碎片则在内核和用户级空间都会存在。 所有分配出去的资源都应该回收,没错,理论上是这样。但实际上很难全部回收,或多或少都存在着一些资源泄漏。应为对有些资源的跟踪相当困难。特别是现在的OS将很多服务都放在Ring0来运行,就更难避免资源泄漏了。 |
|
返回顶楼 | |