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
|
|
|
qiezi
2007-09-16
redsea 写道 fiber 的内存不是从 gc 申请的吗? 直接 malloc 的 ?
qiezi 写道 Fiber的的栈可是要切换的,所以说有很多栈. 如果从堆上分配一块内存,把它切换成执行栈,就有另一个栈被切换出去,目前GC只是扫描线程栈而已. GC只扫描当前线程线和GC里分配的内存,而Fiber的栈上可能有临时分配的对象,GC不会扫描到这部分. 从GC申请有什么用?关键是GC什么时候回收它,在使用过程中被回收可是很危险的,你必须告诉GC你还在使用这块内存,Fiber的栈对于GC来说是看不见的,所以扫描堆栈根本扫不到这边。 |

