D 的 gc, 哪位大大比较清楚, 请解惑
redsea
2007-09-11
看了看 tango gcx.d 里面的 mark 函数, 扫描一段内存的时候, 似乎将里面每个值(32bit 下, 就是每连续4个字节了) 都当指针, 查找对应这个值, 有没有登记在册的内存块, 有的话, 认为这是指针 ?
应该没有这么蛮力吧? --- 这样蛮力的话, 将做无法实现内存块搬移 哪位大大比较清除的, 请解解惑, 谢谢啦. void mark(void *pbot, void *ptop) { void **p1 = cast(void **)pbot; void **p2 = cast(void **)ptop; uint changes = 0; //printf( "marking range: %p -> %p\n", pbot, ptop ); //if (log) debug(PRINTF) printf("Gcx::mark(%x .. %x)\n", pbot, ptop); for (; p1 < p2; p1++) { Pool *pool; byte *p = cast(byte *)(*p1); //if (log) debug(PRINTF) printf("\tmark %x\n", p); if (p >= minAddr) { pool = findPool(p); if (pool) { size_t offset = cast(uint)(p - pool.baseAddr); uint biti; uint pn = offset / PAGESIZE; Bins bin = cast(Bins)pool.pagetable[pn]; //debug(PRINTF) printf("\t\tfound pool %x, base=%x, pn = %d, bin = %d, biti = x%x\n", pool, pool.baseAddr, pn, bin, biti); // Adjust bit to be at start of allocated memory block if (bin <= B_PAGE) { biti = (offset & notbinsize[bin]) >> 4; //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); } else if (bin == B_PAGEPLUS) { do { --pn; } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS); biti = pn * (PAGESIZE / 16); } else { // Don't mark bits in B_FREE or B_UNCOMMITTED pages continue; } //debug(PRINTF) printf("\t\tmark(x%x) = %d\n", biti, pool.mark.test(biti)); if (!pool.mark.test(biti)) { //if (log) debug(PRINTF) printf("\t\tmarking %x\n", p); pool.mark.set(biti); if (!pool.noscan.test(biti)) { pool.scan.set(biti); changes = 1; } log_parent(sentinel_add(pool.baseAddr + biti * 16), sentinel_add(pbot)); } } } } anychanges |= changes; } |
|
oldrev
2007-09-11
没仔细研究过它们的GC实现,不过 DM 网站上说的大概原理就是彻底扫描堆栈和寄存器,还真是暴力的说
|
|
qiezi
2007-09-14
扫描堆栈是肯定要的,不过扫描的前提是这些指针在堆栈上是总线对齐的.这个还算容易做到,不过tango里面的Fiber又如何处理呢?那可是很多个堆栈..我看它的GC好像也没作特别处理,但这个也不可能在其它地方处理了.
|
|
redsea
2007-09-14
qiezi 写道 扫描堆栈是肯定要的,不过扫描的前提是这些指针在堆栈上是总线对齐的.这个还算容易做到,不过tango里面的Fiber又如何处理呢?那可是很多个堆栈..我看它的GC好像也没作特别处理,但这个也不可能在其它地方处理了.
fiber 用的内存如果是从 gc 中申请的, 那不会有问题啊. 从 gc 申请内存的时候, 会打上标志, 这个内存是否需要 scan. 如果你申请的内存是 byte[], 那么不会需要 scan, 如果是 Object[], void[] 之类的, 需要 scan. 在mark 的过程中, 从 root 开始, 只要有一个 pointer(可能并非一个真pointer, 只是对齐的几个字节而已) 指向一个内存块, 这个内存块被 mark 为需要scan, 那么这个内存会被当作所有都是指针, 被 scan. 所以, 这个gc 实现会出现一些内存实际上没有被用, 但是不被释放的 --- 你的其他数据被当作pointer 了, 刚好指向某个 block . 根据这点, 我想, 定义数据结构的时候, 大型的静态数组最好就不要和其他指针, 对象同时放在一个 struct/class 里面, gc 就可以降低一些工作负担. |
|
dayn9
2007-09-14
“其他数据被当作pointer 了”
这种碰撞几率应该极低. |
|
qiezi
2007-09-14
redsea 写道 qiezi 写道 扫描堆栈是肯定要的,不过扫描的前提是这些指针在堆栈上是总线对齐的.这个还算容易做到,不过tango里面的Fiber又如何处理呢?那可是很多个堆栈..我看它的GC好像也没作特别处理,但这个也不可能在其它地方处理了.
fiber 用的内存如果是从 gc 中申请的, 那不会有问题啊. 从 gc 申请内存的时候, 会打上标志, 这个内存是否需要 scan. 如果你申请的内存是 byte[], 那么不会需要 scan, 如果是 Object[], void[] 之类的, 需要 scan. 在mark 的过程中, 从 root 开始, 只要有一个 pointer(可能并非一个真pointer, 只是对齐的几个字节而已) 指向一个内存块, 这个内存块被 mark 为需要scan, 那么这个内存会被当作所有都是指针, 被 scan. 所以, 这个gc 实现会出现一些内存实际上没有被用, 但是不被释放的 --- 你的其他数据被当作pointer 了, 刚好指向某个 block . 根据这点, 我想, 定义数据结构的时候, 大型的静态数组最好就不要和其他指针, 对象同时放在一个 struct/class 里面, gc 就可以降低一些工作负担. Fiber的的栈可是要切换的,所以说有很多栈. 如果从堆上分配一块内存,把它切换成执行栈,就有另一个栈被切换出去,目前GC只是扫描线程栈而已. GC只扫描当前线程线和GC里分配的内存,而Fiber的栈上可能有临时分配的对象,GC不会扫描到这部分. |
|
redsea
2007-09-15
dayn9 写道 “其他数据被当作pointer 了”
这种碰撞几率应该极低. 32bit value, 总共的值域有 4G 个不同的值. 如果我的程序用了1G的内存空间, 那么 1/4 的几率冲突. |
|
redsea
2007-09-15
fiber 的内存不是从 gc 申请的吗? 直接 malloc 的 ?
qiezi 写道 Fiber的的栈可是要切换的,所以说有很多栈. 如果从堆上分配一块内存,把它切换成执行栈,就有另一个栈被切换出去,目前GC只是扫描线程栈而已. GC只扫描当前线程线和GC里分配的内存,而Fiber的栈上可能有临时分配的对象,GC不会扫描到这部分. |
|
oldrev
2007-09-15
http://www.digitalmars.com/d/garbage.html
|
|
qiezi
2007-09-16
redsea 写道 fiber 的内存不是从 gc 申请的吗? 直接 malloc 的 ?
qiezi 写道 Fiber的的栈可是要切换的,所以说有很多栈. 如果从堆上分配一块内存,把它切换成执行栈,就有另一个栈被切换出去,目前GC只是扫描线程栈而已. GC只扫描当前线程线和GC里分配的内存,而Fiber的栈上可能有临时分配的对象,GC不会扫描到这部分. 从GC申请有什么用?关键是GC什么时候回收它,在使用过程中被回收可是很危险的,你必须告诉GC你还在使用这块内存,Fiber的栈对于GC来说是看不见的,所以扫描堆栈根本扫不到这边。 |