关于gc的想不明白

77 views
Skip to first unread message

Huafeng Mo

unread,
Oct 9, 2007, 8:40:23 PM10/9/07
to pongba
我想不明白一件事。
我看到的一些gc介绍,都直眉瞪眼地说内存用完了放在那里,等gc一块儿收掉。而智能指针,则是引用结束,便主动释放内存。所以智能指针处理不了循环引用。
我就开始迷糊了。对gc而言,干嘛要傻愣愣地把所有内存都放在一起收集呢?
智能指针很好地把无循环引用的内存即时释放,为什么gc不这么干呢?无循环引用的内存即时释放,gc负责释放那些存在循环引用的,这样不就大大减少gc扫描的内存了吗?
这就好比一屋子人吃瓜子,大家都把壳扔在地上,待会儿扫起来当然费劲。如果大家都直接把壳放进垃圾桶里,一会儿只需对付散落在外的少量遗漏,就省力多了嘛。
这里面有什么理论或技术上的障碍吗?哪位能给我解释解释,先谢过了。:)

--
反者道之动,弱者道之用

red...@gmail.com

unread,
Oct 9, 2007, 9:41:24 PM10/9/07
to pon...@googlegroups.com
确实可以象你说的这样用, 而且对于单线程程序, 这样用效果应该也还可以.

但是, 多线程程序下, 如果所有指针, 对象默认都用同步锁的方法保护 addref,
release, 代价就很高了, 同步锁不但影响当前加解锁的cpu, 而且通过对内存总线
加锁影响所有的cpu. ---- btw: 如果每个对象都有自己独立的锁, 那么这个程序
的 startup 时间也会很恐怖了; 如果一类对象有一个独立的锁, 那么这个程序的
并发度大受影响.

如果不是默认就用同步锁, 而是用户可以指明用不用同步锁, 那么对用户而言的负
担也很重.

不过我觉得智能指针确实值得作为一个配合的工具放到基本工具箱中.

Huafeng Mo 写道:

Huafeng Mo

unread,
Oct 9, 2007, 9:55:41 PM10/9/07
to pon...@googlegroups.com
哦,原来是并发问题。一直在单线程,脑子转不过弯来。:(
另外再问一下,有没有其他办法,而不是简单的引用计数,实现引用控制,而不会引发并发问题的呢?我知道可以用链表实现智能指针,它的并发特性如何?还有其他方法吗?
还有就是,如果编译器的gc负责引用控制,(编译器可以获得更多的内存信息),是否会好一些?
最后,未来的并发技术发展是否有能力化解引用控制在并发上的问题?

不好意思,我是十万个为什么。:P


在07-10-10,red...@gmail.com <red...@gmail.com> 写道:
--
反者道之动,弱者道之用

怀宇范

unread,
Oct 9, 2007, 10:12:25 PM10/9/07
to pon...@googlegroups.com
垃圾回收是独立的线程来负责,并发对效率的影响很大。就算不考虑并发,如果有一点内存释放就回收也可能带来很多问题,因为有时候回收点不好会引起复杂度较高的运算,所以理想托管内存的回收都是在实在没有可分的内存的时候进行的,时间也选择在系统负担比较轻的时候进行。
垃圾回收的算法最经典思想包括引用计数,标记清扫,节点复制等等,不只是引用计数一种。具体的细节和评定很难说了,建议看一些书和论文。
如果有编译器的支持,gc的实现自然会简单很多,效率也更高,所以期待C++0x。。。

 
在07-10-10,Huafeng Mo <longsh...@gmail.com> 写道:
在07-10-10,red...@gmail.com < red...@gmail.com> 写道:

you.GoTo("14#344, Tsinghua") && you.Find("范怀宇"))return true;
    if(you.MailTo(" dugu...@gmail.com ") || you.MailTo(" fan...@mails.tsinghua.edu.cn "))return true;
    if(you.PhoneTo("13488810330") || you.PhoneTo("01062778689"))return true;
    if(you.QQTo("120628812") || you.MSNTo(" dugu...@hotmail.com"))return true;
    if(you.NetTo(" www.cnblogs.com/duguguiyu "))return true;

    if(you.DareToKiss("FuRongJJ"))
    {
        cout<<"I 服了 U"<<endl;
        return true;
    }
    if(you.GiveMeTwoTicketsFor("MayDay"))
    {
        cout<<"I love you!"<<endl;
        return true;
    }

    return false;
}

pongba

unread,
Oct 9, 2007, 10:13:58 PM10/9/07
to pon...@googlegroups.com


On 10/10/07, 怀宇范 <dugu...@gmail.com> wrote:
垃圾回收是独立的线程来负责,并发对效率的影响很大。就算不考虑并发,如果有一点内存释放就回收也可能带来很多问题,因为有时候回收点不好会引起复杂度较高的运算,所以理想托管内存的回收都是在实在没有可分的内存的时候进行的,时间也选择在系统负担比较轻的时候进行。
垃圾回收的算法最经典思想包括引用计数,标记清扫,节点复制等等,不只是引用计数一种。具体的细节和评定很难说了,建议看一些书和论文。
如果有编译器的支持,gc的实现自然会简单很多,效率也更高,所以期待C++0x。。。

谢之易译的《垃圾收集》。
唯一一本系统讲述垃圾收集机制的书。


--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba

怀宇范

unread,
Oct 9, 2007, 10:43:08 PM10/9/07
to pon...@googlegroups.com
恩。。。我也是看的那本。。。96年的书04年才翻译。。。。

在07-10-10,pongba <pon...@gmail.com> 写道:

Huafeng Mo

unread,
Oct 9, 2007, 10:45:50 PM10/9/07
to pon...@googlegroups.com


在07-10-10,怀宇范 <dugu...@gmail.com> 写道:
垃圾回收是独立的线程来负责,并发对效率的影响很大。就算不考虑并发,如果有一点内存释放就回收也可能带来很多问题,因为有时候回收点不好会引起复杂度较高的运算,所以理想托管内存的回收都是在实在没有可分的内存的时候进行的,时间也选择在系统负担比较轻的时候进行。

我不是这个意思,我不是说有一点就回收,我是说用完了就归还。回收需要扫描和计算,但归还由内存持有方主动操作,只涉及内存释放,不需要扫描。智能指针是这种主动内存管理,而完全的gc则是被动的内存管理。

垃圾回收的算法最经典思想包括引用计数,标记清扫,节点复制等等,不只是引用计数一种。具体的细节和评定很难说了,建议看一些书和论文。

gc的方案我知道,我想知道的是引用控制,也就是智能指针的实现方法还有哪些。

如果有编译器的支持,gc的实现自然会简单很多,效率也更高,所以期待C++0x。。。 

这一点我还是有些担心,java和C# 的gc也是由编译器支持的,几乎成了gc的反面教材。我想知道的是编译器层面的引用控制,相比库形式的引用控制会有多大的优势。

我觉得gc的核心还是引用控制和管理。一块内存没有引用,自然可以回收。智能指针利用RAII实现对引用的跟踪,继而在最后一个引用断开之前释放内存。而gc则通过扫描确定内存的引用状态。我是觉得两种方式结合起来,能主动归还的就尽量主动归还,不能的就采用被动的方式处理。可以获得更多的性能优势。



--
反者道之动,弱者道之用

red...@gmail.com

unread,
Oct 9, 2007, 10:49:48 PM10/9/07
to pon...@googlegroups.com
怀宇范 写道:
> 垃圾回收是独立的线程来负责,并发对效率的影响很大。就算不考虑并发,如果
> 有一点内存释放就回收也可能带来很多问题,因为
我没有分析过很多 gc, 但是 D 语言就没有使用独立线程.

> 有时候回收点不好会引起复杂度较高的运算,
> 所以理想托管内存的回收都是在实在没有可分的内存的时候进行的,
没有内存可分的时候再进行 gc, 确实是理想点吗 ? 我有这个怀疑. 这种做法是一
种"自私做法", 认为整个系统只有一个进程需要内存,连 OS 都不需要.

如果一个服务器上只跑一个这样程序, 而且这个服务器上没有多少文件操作或者网
络操作 ---- 因此不需要多少 buffer 和 cache 这种东西, 这个说法肯定成立,
但是似乎只有科学计算的时候, 才会如此吧, 我们平常似乎没有多少机会碰到这样
的业务.

> 时间也选择在系统负担比较轻的时候进行。垃圾回收的算法最经典思想包括引用
> 计数,标记清扫,节点复制等等,不只是引用计数一种。具体的细节和评定很难

redsea

unread,
Oct 16, 2007, 4:09:20 AM10/16/07
to TopLanguage
> 但是,多线程程序下, 如果所有指针, 对象默认都用同步锁的方法保护 addref,
> release, 代价就很高了, 同步锁不但影响当前加解锁的cpu, 而且通过对内存总线

我说的也有一些问题, reference count 其实可以使用 atomic_add 和 atomic_dec 做, 这样就不必使用同步
锁了, 开销会小很多.

如果编译器再进行优化, 例如
1. 本 scope 前面某个对象的操作已经 addref, 后续的操作就不在 addref, dec; 或者一个循环中,
2. 循环的优化, 某个变量从对象数组中取值, 对象数组没有修改的这种情况, 不用 addref, dec
等, refercence count 的开销应该就会继续减小, 这样 Huafeng Mo 的想法: 平时可以释放的, 就及时释放,
refercence count 解决不了的, 才gc, 这个做法, 多线程下面, 似乎成本也不是很高了.

Huafeng Mo

unread,
Oct 16, 2007, 4:48:07 AM10/16/07
to pon...@googlegroups.com
明白了,明白了。多谢指点。:)
记得sutter的more exceptional c++里面有这么一个类似的案例,回去查查看。:)

在07-10-16,redsea <red...@gmail.com> 写道:



--
反者道之动,弱者道之用
m...@seaskysh.com
longsh...@gmail.com
http://blog.csdn.net/longshanks/

Huafeng Mo

unread,
Oct 16, 2007, 8:25:14 PM10/16/07
to pon...@googlegroups.com
又想到一个问题。
现在,智能指针以类库形式实现。如果由编译器实现,那么是否会有更好的效果?


在07-10-16,Huafeng Mo <longsh...@gmail.com> 写道:

red...@gmail.com

unread,
Oct 16, 2007, 8:43:29 PM10/16/07
to pon...@googlegroups.com
那应该是啊, 假设编译器在 ObjPtr 上实施智能指针语义管理, 那么下面这段代码
void (const ObjPtr in)
{
ObjPtr t1, t2;

t1=in;
...
t2=t1;

...
}

编译器实现, 就可以知道, 这个对象不会通过t1, t2释放(由于 in 会keep obj 的
count >0), 中间的赋值等等东西, 都不必去addref, release 了; 即使这些代码
是属于80% 的性能不关键代码, 好歹可以降低workingset, 让 cpu cache 更有效工作.

在对一个大型智能指针数组/容器进行遍历处理的时候, 如果能够将这些 addref,
release 优化掉, 性能的提升就更明显了.

这种用库是很难做到的.


Huafeng Mo 写道:

Oxygen

unread,
Oct 19, 2007, 1:30:46 PM10/19/07
to TopLanguage

On 10月16日, 下午5时09分, redsea <red...@gmail.com> wrote:
> > 但是,多线程程序下, 如果所有指针, 对象默认都用同步锁的方法保护 addref,
> > release, 代价就很高了, 同步锁不但影响当前加解锁的cpu, 而且通过对内存总线
>
> 我说的也有一些问题, reference count 其实可以使用 atomic_add 和 atomic_dec 做, 这样就不必使用同步
> 锁了, 开销会小很多.

单核的时候atomic_add 和 atomic_dec就是摆设。
多核的时候atomic_add 和 atomic_dec一样要锁总线,使Cache行无效,开销不见得小。
如果把指针作为某个结构的member的话,就比较可怕了,每次改变指针,整个结构就要重新装载。

> 如果编译器再进行优化, 例如
> 1. 本 scope 前面某个对象的操作已经 addref, 后续的操作就不在 addref, dec; 或者一个循环中,
> 2. 循环的优化, 某个变量从对象数组中取值, 对象数组没有修改的这种情况, 不用 addref, dec
> 等, refercence count 的开销应该就会继续减小, 这样 Huafeng Mo 的想法: 平时可以释放的, 就及时释放,
> refercence count 解决不了的, 才gc, 这个做法, 多线程下面, 似乎成本也不是很高了.

有分支,有异常的情况下编译器似乎很难静态推测出那些addref和decref是可以省略的。
而且很多GC会压缩内存,这时候尽快释放也不适合。而是积累了一定的量再释放比较好。

清风雨

unread,
Oct 22, 2007, 10:10:29 PM10/22/07
to TopLanguage
在最开始学习java时,知道GC机制,那时是非常的为其所吸引。现在用了比较久的C++以后,又被程序员控制所吸引。
内存由程序员控制的主动方式,在性能上应该要优胜被动方式吧。毕竟被动的策略有限,而主动的策略就多了。

不过现在听说很多高人普遍倾向GC,不是很清楚。上次看D语言的GC说明,也还是没有解决我上面的疑惑。

Reply all
Reply to author
Forward
0 new messages