返回   cpper编程论坛 > 技术杂烩
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

技术杂烩 找不到地方的技术问题?这里!

回复
 
LinkBack 主题工具 显示模式
  #1 (permalink)  
旧 2008-02-01
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 <道不同不相为谋,此帖已结>请教各位大大关于动态内存管理的问题……

现在网上google到的文章里一般都是给gc说好话的,gc的问题则很少看到有人说。对于动态内存管理,之前一直是直接new/delete,最近一段时间思考了一下,觉得本来大多数时候应该充分利用桟来进行管理,其次使用内存池,再然后使用基于引用计数的智能指针,最后才使用gc。我在网上看到的一些文章的态度,大有gc一统天下的味道,而我在qq群里跟别人讨论时,也有人跟我持类似的看法:gc只是最后的手段,只是防止内存泄漏的底线手段。
在网上找到一篇文章《The Measured Cost of Conservative Garbage Collection》,根据里面的结论, gc在CPU消耗方面没有多少劣势(虽然也没有多少优势),但gc在内存换页和cache miss方面的问题比较严重,并且也会占用比手工管理或 allocator更多的内存。如果真是这样,那么gc在应用于比较大的系统时,换页和cache miss可能会有损时间性能。只是这篇文章是1993 年的,所以想问问现在的gc在局部性以及内存占用方面做得怎么样了。
我还注意到,gc一般都有比较大的不确定性(如果引用计数不算gc的话),并且网上有人认为RAII比较难以和gc相结合,一方面回收的时机可能会滞后而浪费资源,另一方面gc 在回收时不一定保证会调用析构函数,据说因为析构函数可能会使gc变得非常复杂。我觉得如果不能把非内存资源绑定到内存管理机制上,那么非内存资源仍然会面对手工管理动态内存时的过早释放或过晚释放问题,这样gc几乎就成了鸡肋。不知道大家怎么看这个问题。另外,我猜测局部性差的问题也是由不确定性引起的,因为gc倾向于延迟对内存的回收,因此申请内存时有更大的概率使空间占用变得分散。
最后,关于gc引起的停顿问题,我看到的解决办法是使用增量式gc。但是,如果gc频率低了,那么单次gc的工作量会倾向于比较大;如果gc频率高了,线程切换以及锁的开销又会影响性能。因此我怀疑增量式gc到底能不能在高负荷环境下用于对延迟比较敏感的程序,比如MMO服务器端。
当然以上都只是猜测,网上搜gc搜到的都是大把的赞誉之辞,所以特地来这里打听一下gc的缺陷和不适用的情况。;)

另外就是关于引用计数,由于它的确定性比gc高并且比较容易和RAII结合,所以我比较倾向于用引用计数。但是自然就要面对循环引用的问题。一种做法是用弱引用,但这样其实是把问题抛回给使用者,所以我尽量不这么做。后来我查到python里有一个循环引用探测机制,只是还不知道其具体的思想和方法,哪位大大知道的话可否简单讲解一下。^_^
关于循环引用,我思考之后是这么看的:首先假设对象全都是在特定的内存池上分配的,并且全部使用封装好的智能指针来访问。
对于一个对象,如果指向它的所有引用都在桟上,那么它一定不会牵扯到循环引用的问题,因为桟能保证桟上的引用(也就是智能指针)迟早都会被删除,这样这个对象的引用计数一定能够降到0。
对于一个对象,如果它会牵涉到循环引用而发生泄漏,那么一定存在至少一个指向它的智能指针,且该智能指针自身也在堆上(即内存池里)。所以循环引用的问题,是因为引用计数本身是以桟这种自动机制为基础的,堆上的智能指针没有了桟的支持就无法完成自动管理的任务。
所以,我在想,如果把引用计数分成两个计数器,一个记录直接外部引用的数目,另一个记录间接外部引用的数目。
直接外部引用指的是在桟上的、直接指向该对象的引用。间接外部引用,这些引用也是直接指向该对象的,但是它们都不在桟上,而是在堆上(因此也需要其他智能指针来维持其生存),并且在整个系统中,存在某个桟上的引用能够间接的、(并最终)通过该引用而得到该对象。直接外部引用可能会转变为间接外部引用,但反过来不会。
打破循环引用往往只需要删除某一个对象,就能引起链式反应。但是具体如何使用上面这种2个计数器的机制来打破循环引用,我还没考虑好,一时也想不出新的东西,所以来问下各位大大,有些什么看法~

此帖于 2008-03-12 11:09 AM 被 sjinny 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #2 (permalink)  
旧 2008-02-03
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,763
Elminster 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

这些说来话长,有兴趣的话去看《垃圾收集》这本书吧:

[url=http://www.china-pub.com/computers/common/info.asp?id=18468]
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #3 (permalink)  
旧 2008-02-03
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

终于有人回复了…… T.T

其实,我现在最想看到的是给gc揭短的文章,因为兜售gc的文章已经太多了……

其实,gc只是内存自动管理机制的一种,而且还带有很大的不确定性……桟就是一种自动机制,只不过它的生存期模型很特别,所以很多情况下表达能力不够。所以我想,也许可以思考其他的生存期模型,不一定是桟,而是其他的模型,但是确定性仍然要比较高……如果能有更多的像桟这样的确定型的自动模型,那么依靠它们的互补也许就可以做到在绝大多数情况下不需要使用gc这种高度不确定的模型了……
我很少看到文章从这个角度考虑问题。但是前几天搜索到一些论文,虽然也比较老,但是其中谈到了基于Regions的管理方法,其实就相当于内存池,但是是变成了一种语言特性。这是我所看到的唯一有点接近上面那个桟机制的思考角度的文章……

Elminster有什么看法不?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #4 (permalink)  
旧 2008-02-03
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

如果从语言角度看,GC不是个可选择的问题。
例如LISP等函数式语言,很难想象没有GC,而要求程序员手工释放会是什么样子。
这种语言中,根本没有new之类的语法,所有内存操作都是不透明的。

就好比用户使用计算器,如果计算结果需要手工释放,其usability就成问题了。

我觉得在现实工程中,有相当一部分问题,可以通过这样的极端方式解决。

1. 完全不使用包括GC在内的任何回收机制,用户随意使用内存。
2. 程序结束退出后,由操作系统回收全部资源。

即使在内存不足的20年前,对于排序,统计,这类一次性计算的任务。这一方式
也有效。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #5 (permalink)  
旧 2008-02-03
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: liuxinyu 查看帖子
如果从语言角度看,GC不是个可选择的问题。
例如LISP等函数式语言,很难想象没有GC,而要求程序员手工释放会是什么样子。
这种语言中,根本没有new之类的语法,所有内存操作都是不透明的。

就好比用户使用计算器,如果计算结果需要手工释放,其usability就成问题了。

我觉得在现实工程中,有相当一部分问题,可以通过这样的极端方式解决。

1. 完全不使用包括GC在内的任何回收机制,用户随意使用内存。
2. 程序结束退出后,由操作系统回收全部资源。

即使在内存不足的20年前,对于排序,统计,这类一次性计算的任务。这一方式
也有效。
如果某个语言没了gc就不能活,只能说明语言的设计出了问题:这个语言吊死在了gc这一棵树上。其实对于一下java和c++,java一直以“完全的面向对象”为宣传亮点,而C++的特点则是综合了多种不同的范式,包括面向过程、面向对象、GP等(说“等”是因为C++以后仍然可能继续添加新的范式)。其实java的所谓完全的面向对象,只不过是它的编程范式多样性匮乏的另一种说法,几乎是在把缺点当作优点来大肆炒作。C++09的gc也是程序员可以自己控制的,而不像java那样强加于人。java所缺乏的就是C++的包容性和对程序员的选择权的尊重。

汗,好像扯远了……
总之,从语言设计的角度看,gc就是一个可以选择的东西。因为是否使用gc是针对具体应用进行分析时所作的选择,也许某些机械的事情程序员做得不如程序好,比如增加和减少refCount,但是是否使用gc这种决策,应该相信没有机器能做得比人好,也没有人能够在设计语言时就替别人做出选择。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #6 (permalink)  
旧 2008-02-04
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,763
Elminster 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
终于有人回复了…… T.T

其实,我现在最想看到的是给gc揭短的文章,因为兜售gc的文章已经太多了……

其实,gc只是内存自动管理机制的一种,而且还带有很大的不确定性……桟就是一种自动机制,只不过它的生存期模型很特别,所以很多情况下表达能力不够。所以我想,也许可以思考其他的生存期模型,不一定是桟,而是其他的模型,但是确定性仍然要比较高……如果能有更多的像桟这样的确定型的自动模型,那么依靠它们的互补也许就可以做到在绝大多数情况下不需要使用gc这种高度不确定的模型了……
我很少看到文章从这个角度考虑问题。但是前几天搜索到一些论文,虽然也比较老,但是其中谈到了基于Regions的管理方法,其实就相当于内存池,但是是变成了一种语言特性。这是我所看到的唯一有点接近上面那个桟机制的思考角度的文章……

Elminster有什么看法不?
GC 的不确定性一般来说不是什么问题,除非你是在为战斗机写火控系统,必须有可证明的硬实时要求。现在研究其他内存管理机制的文章如此之少,其实也从一个侧面证明了 GC 作为一种通用的内存管理机制并没有遇到太多的麻烦。

引用:
作者: sjinny 查看帖子
如果某个语言没了gc就不能活,只能说明语言的设计出了问题:这个语言吊死在了gc这一棵树上。其实对于一下java和c++,java一直以“完全的面向对象”为宣传亮点,而C++的特点则是综合了多种不同的范式,包括面向过程、面向对象、GP等(说“等”是因为C++以后仍然可能继续添加新的范式)。其实java的所谓完全的面向对象,只不过是它的编程范式多样性匮乏的另一种说法,几乎是在把缺点当作优点来大肆炒作。C++09的gc也是程序员可以自己控制的,而不像java那样强加于人。java所缺乏的就是C++的包容性和对程序员的选择权的尊重。
没有 GC 不能活的语言很多,而且其中颇有一些是非常伟大的设计,比如 lisp 语言家族。

引用:
作者: sjinny 查看帖子
汗,好像扯远了……
总之,从语言设计的角度看,gc就是一个可以选择的东西。因为是否使用gc是针对具体应用进行分析时所作的选择,也许某些机械的事情程序员做得不如程序好,比如增加和减少refCount,但是是否使用gc这种决策,应该相信没有机器能做得比人好,也没有人能够在设计语言时就替别人做出选择。
GC 是不是一个可选项,取决于这个语言打算支持什么样的程序设计范式。如果打算支持函数式程序设计范式,那么 GC 就是必须的;同样的,书写面向对象程序的程序员如果能有 GC 的支持,也会舒服得多,能够应用更多可以提高生产力的技术。是否使用 GC 这种决策,确实没有机器能够比人做得好,不过设计语言的时候做出这个决策却一点问题也没有:虽然你不能决策这个语言是否内建 GC,但是你可以决策究竟使用哪种语言,不是么?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #7 (permalink)  
旧 2008-02-04
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

嗯,也许不必再讨论要不要语言内建gc了……
从我的角度看,gc就是依靠自身的不确定性来应对问题之中的不确定性。而桟是用确定性来应对问题中确定的那个部分。我更倾向于用更多的确定型的办法互相补充配合来解决问题。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #8 (permalink)  
旧 2008-02-15
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

完美的确定性会带来太多的不便,从而导致工程上不可行。stack确实是确定的,但是它的表达能力并不够强。所以我们才需要heap (或者说为了得heap的表达能力代价太大). 那gc和stack作比较本来就没啥用。stack很优雅,但heap很有用。工程上可能更需要一些有用的东西吧。

.NET宣传它的GC在generation 0的一次collect平均只需要1不到毫秒,所以对performance的影响也没有想象中那么巨大。(也可以用perfmon看到,我姑且相信他的说法……)主要原因是generation 0的大小正好可以放在L1缓存,但现如今的L1 cache都够大了,放好多个string什么的没啥问题,何况还有大对象自己的GC generation.

另外扯出去,C++做的难用感觉上一个原因就是它目的太多,又要最底层的操控又要高级的抽象。另外感觉上当初设计的时候也没考虑周全…… sigh

此帖于 2008-02-15 09:16 PM 被 cat 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #9 (permalink)  
旧 2008-02-15
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

不知道你的“完美的”确定性是个什么样的概念……
我觉得确定性本身不会带来不便,带来不便的只是有限的表达能力,这跟确定性不是一回事。
stack的表达能力的确有限,但是在其表达能力之内的事情,它却能处理得很好。我觉得我们需要的是更多像stack这样的确定型的生存期模型,用来填补stack的表达空白。
使用gc,其实丢弃了很多信息,我觉得这是种浪费。可以说gc可以让程序员免于生存期分析,但是这样的安逸自然也有代价。所以在我眼里,gc永远都是下下策,永远都是别无他法时的选择。
gc的性能不仅要考虑一次collect的时间,还要考虑对cache miss和内存换页的影响。

C++难用的原因,我结合其他人的文章,我觉得是人们误用的程度比C++自身的难用更严重。C++想要做成通用目的的语言,但是使用的时候人们却往往想要既全面又深入的理解C++,这是不必要的。根据自己的需要来选择C++中的特性,我觉得这样C++就不是那么的难用。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #10 (permalink)  
旧 2008-02-16
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,763
Elminster 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
不知道你的“完美的”确定性是个什么样的概念……
我觉得确定性本身不会带来不便,带来不便的只是有限的表达能力,这跟确定性不是一回事。
stack的表达能力的确有限,但是在其表达能力之内的事情,它却能处理得很好。我觉得我们需要的是更多像stack这样的确定型的生存期模型,用来填补stack的表达空白。
使用gc,其实丢弃了很多信息,我觉得这是种浪费。可以说gc可以让程序员免于生存期分析,但是这样的安逸自然也有代价。所以在我眼里,gc永远都是下下策,永远都是别无他法时的选择。
gc的性能不仅要考虑一次collect的时间,还要考虑对cache miss和内存换页的影响。

C++难用的原因,我结合其他人的文章,我觉得是人们误用的程度比C++自身的难用更严重。C++想要做成通用目的的语言,但是使用的时候人们却往往想要既全面又深入的理解C++,这是不必要的。根据自己的需要来选择C++中的特性,我觉得这样C++就不是那么的难用。
你不妨先说说 GC 回收内存时机的不确定性究竟有什么问题,让你如此深恶痛绝以至于认为它“永远都是下下策”?你说使用 GC “其实丢弃了很多信息”又是指什么?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #11 (permalink)  
旧 2008-02-16
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

首先我假设任何内存块的回收时机都存在一个事实上的最佳时机。过早的回收会导致程序出错,就不去考虑它了。而过晚的释放会有这样几种趋势影响:
1.倾向于占用更多的内存。
2.倾向于导致更高的cache miss概率和内存页面错误率。因为如果及时的回收,将能够使得内存占用的分布更加的紧凑,而且这种紧凑是不需要对对象进行复制的。内存回收的滞后会倾向于使内存占用铺得更开。
3.难以把其他资源的管理绑定到内存管理上。首先,不管内存怎么降价,它都不是免费的资源。第二,还有其他的资源可能比内存更加宝贵,无论是os资源、硬件资源,还是应用程序自己的各种资源,都不能假设它们便宜到“不需要尽快回收”的地步,毕竟将来产生的新资源不应该受这种假设的约束。第三,就我所知,RAII是把各种资源绑定到内存上进行管理的一种比较好的办法,但是它是用析构函数把内存的释放和其他资源的释放绑定到一起的。第四,内存释放的滞后意味着其基础上的RAII对其他资源的释放也是滞后的,更重要的是,如果没有手动的调用collect,那么gc对内存及绑定其上的其他资源的释放时机是未知的,虽然可能不会等多久,但是我们无法确认其释放是否能够做到“及时”。如果手动调用collect,但是这个collect是针对所有类型的对象的,那么各种不同生命期模型的对象会使collect的时机很难正确把握,毕竟用了gc就说明这些对象的生命期模型对于程序员来说不是非常明确的。如果collect是有很强的针对性的,那么我不认为这是gc,我会认为这是内存池。在我看来,内存池与gc的不同是,使用gc时程序员对各种对象的生命期的知识是非常少的;使用内存池时,虽然程序员无法明确各个对象的生命期细节,但是对于某个对象集合的生命期还是比较了解的,所以才能把握好释放内存池的恰当的时机。虽然内存池可以与gc的一些技术结合(比如内存扫描),但是其设计思想上的分界线在我看来还是比较明显的,并且结合后所引入的内存扫描也只是这种内存池的辅助功能而非关键。
4.gc线程与应用程序之间的切换可能会带来一些问题。如果不是增量gc,那么会引起应用程序停顿的问题。如果是增量gc,那么相对于非增量gc会产生更多的线程切换和竞争。如果可以讨论极端情况,那么一个极端是gc的频率很低,于是导致非增量gc那样的应用停顿问题;如果gc的频率很高,那么线程切换和竞争也会降低整体效率,仍然可能使应用的响应变慢。理论上如果在这两个极端之间有一个良好的平衡点,那么以上的极端问题都不会严重。但是对于应用环境会变化的应用来说,寻找这样的平衡点也是个麻烦。在以后CPU内核越来越多的环境中,我怀疑寻找这样的平衡点会越来越困难。我觉得,gc可能会在同一个线程中处理很多原本是低耦合的内存块,这些内存可能由很多不同的线程使用,它们本身没有太多关联,但是如果要由同一个gc线程来处理它们,那么就认为使它们发生了耦合,结果就是使得原本低耦合的各个应用线程因为gc而提高了耦合度,线程之间的高耦合会因为竞争而降低性能。当然这最后一点与gc的不确定性关联不大,不过也是我不看好gc的理由之一。虽然猜测的成分很大,不过毕竟让我不放心。

关于gc“丢弃信息”的问题,我举个极端的例子。就我所知,java里程序员无法在桟上建立对象,于是即使是生命期很明确的对象也需要依赖gc或者vm的自动化分析。这个例子中,程序员对对象生命期的知识就无法明确的在程序里表达出来,这就是对知识的丢弃。java的vm好像能够进行自动化的分析,但是这是先让程序员把自己知道的知识丢掉,然后再让程序来进行分析,就我所知目前即使是AI也还没有人那么聪明。


我对这些的理解也很肤浅,所以还请各位大大多多指教口牙…… ^_^!
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #12 (permalink)  
旧 2008-02-16
liuxinyu 的头像
高级会员
 
注册日期: 2006-02-09
帖子: 303
文章: 48
liuxinyu 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

个人觉得就这个问题讨论下去,只能是无休无尽的口水仗。这在很多论坛上都是发生过的。

说说我个人的观点。也许是混的日子长了,我现在不怎么认死理了。
* 我以前相信世上存在最伟大的语言(C++)和一统江湖的方法论(曾经是OO后来是OO+GP),现在我知道每个语言(哪怕是个脚本)都很不简单。并且存在着众多同样优秀的方法论。
* 世上不止只有C++和Java两个作品,也不仅只有design pattern这一组智慧结晶。
* 一把钥匙开一把锁,不存在万能钥匙。如果把计算机语言比作钥匙的话。
* 行万里路,读万卷书,越学习越觉得发现自己知道得太少。

退回到语言和GC的话题上来,我首先推荐的两个例子是lisp和smalltalk,一个是60年代发明的,一个是70年代发明的,那时内存多紧张我就不多说了。相信了解完它们后就可以释怀GC这个题目。因为GC不是个问题。

同样我也推荐看看lambda演算,看看另外一种表达可计算性的体系。并思考在那个体系中GC扮演什么角色。

曾经《程序员》杂志的编辑批评我用C++来写语言解释器是“空对空”,毫无意义。他觉得我应该干些实事,用C++多写一些网络和操作系统方面的东西。我的回答是,C++对我来说就如同数学公式,我只用它来帮助表达思想。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #13 (permalink)  
旧 2008-02-16
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

不知道lisp和smalltalk所针对的应用领域是什么?一开始的gc和现在的gc也不完全一样,那时的应用环境和现在的也不一样。
我也不相信有什么一统江湖的方法论,但是现在对gc的宣传就好像gc将会在内存管理领域内一统江湖了。

说老实话,你的帖子缺乏细节,所以我看了之后仍然没有获得什么信息…… @_@!
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #14 (permalink)  
旧 2008-02-16
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

够用就好,有些华丽丽的东西你能理解也能用好不说明别人也能理解能用好。

再有就是你说“难以把其他资源的管理绑定到内存管理上”让我觉得你希望找到一个通用的资源管理模式。不过实际上各种资源都有他自己的区别,从而也没必要有一个通用的管理模式。比如内存,现在gc很好很快,内存也是白菜价,就不需要太扣,可以减少程序的错误也可以提高写code的速度。但是对于其他一些资源就不是这种情况,比如OS资源和硬件资源,甚至特定环境下的内存资源(对于这种对内存控制要求非常精确的环境,C#/Java等语言直接是“不适用”,用别的语言好了,这些语言也不是为了支持这种应用而设计的)。

另外,我也不觉得会存在什么完美的资源管理模式,总是各有各的优缺点。GC的优点就是方便,而且在目前很多应用里工作的非常好,缺点么你也列了不少不过在某些应用中都可以忽略不计。然后其他管理模式的优点可能是精确,最优化内存使用,但缺点就是不够方便。取得某一方面的优势往往不是免费的。

还有你一直很关心效率问题,但是效率真的可以这么省吃俭用下来吗?在C#/Java所在的应用里面,真正的瓶颈往往在DB, 硬盘, 用户输入,网络延迟等。多了几次线程切换又如何?少了一点cache hit又如何?比起那些来就是小巫见大巫。你看看你得task manager上CPU占用最多的是谁?还不是System Idle Process? 闲着也是闲着,整理整理内存不是正好?不要觉得用了GC地球就会变暖。

另外就是GC的效率问题,对于特定的应用可以自己测测看,就我们用下来效率还是很不错的。.NET的话gen 0的清理本来就利用了cache, 然后分级也解决了不少问题。我们的某个网络服务本来C++写的,担惊受怕到处check空指针到处check HResult到处copy string(有人要说了,怎么这么写C++?? 这是网络服务,7*24运行的,如果因为一个空指针Access Violation然后进程重起虽然后面有人看着总还是不太爽。至于string copy么,传给了某人不知道生存期会如何,就让“某人”copy一份自己留神吧……我还要传给别人呢……).

后来转成了C#测试的latency比原来小了30%. 里面主要的提升是什么?是因为人不够聪明写得太多的null pointer check被优化掉了?因为那些HResult check变成了exception check? (C++也有exception, 但是COM对象太多大家都不用) 因为少了那些string copy? 我也不知道,但个人觉得换了新的CPU更快的硬盘才是最大功臣。

再来点空洞的,某些东西是个好东西,但也有他自己的应用范围,如果把一些不适用的情况下强行去用的话,乍一看很有趣,很cool, 很优雅,但其实隐患无穷。语言也是如此, stack也是如此。

此帖于 2008-02-16 11:45 PM 被 cat 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #15 (permalink)  
旧 2008-02-17
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

看了你的帖子之后我才想起来,我没有说明我所考虑的应用领域……
我所针对的是网络游戏。对于游戏来说,无论是CPU还是内存,都是不能随便挥霍的东西。特别是那些想要有所创新的游戏,更要尽量节省出各种资源,为新的特性留出余地。

我的确是想要找到一种通用的资源管理办法。资源各有不同,这一点我同意,但是这不代表我们不需要通用的管理办法,也不代表我们不可能找到这样的办法。而且,所谓通用的办法,也并不是用一个一成不变的死板的机制来管理,而是在一个通用的机制基础上进行定制。我所理解的RAII,就是在内存管理这个机制的基础上来定制其他资源的管理。内存管理实现了各种内存生命期模型,RAII则把各种资源的生命期与内存生命期绑定起来,这样对于非内存的管理,我们只需要实现这个绑定过程,以及在生命期的开头和结尾的针对性操作,而生命期模型则靠内存管理来维护。我觉得资源管理最困难的不是如何申请和释放资源,而是决定何时释放什么资源。内存管理实现了对内存释放时机的控制,这解决了最大的难题,RAII使得我们不需要为每种不同的资源重复解决这个难题。由于RAII与目前的gc很难结合,所以我觉得gc在提供了一个不错的内存管理机制的同时,恶化了其他资源的管理。

不存在什么完美的资源管理模式,这我也认同。但是,一方面,目前对gc的批评淹没于对gc的追捧之中;另一方面,我觉得应该大力发展其他确定性高的内存管理机制,与stack等高确定性机制形成互补,我觉得这种局面比gc包揽全场要更好,因为我觉得让一群各有所长的专家互相配合会比一个博而不精的人做得更好。

在特定的领域,gc不会带来太多的问题,这一点我同意。但是对于游戏等性能敏感的应用,优化仍然是有必要的。当然其可行性就要看能不能发展出更多的像stack那样优秀的高确定性内存管理机制。

关于空指针,我觉得本来是不需要到处检查的,因为完全可以做个类把指针封装起来,并且对对象进行统一的生命期管理。再结合异常,把出错情况(如访问空指针)集中在一起处理,应该也不会带来太多麻烦。手工编写大量的重复性的代码来进行有效性检测,这似乎更像C而不是C++的做法。另外曾经听别人说过这样的感受:他们觉得随着经验的增长,对new/delete的调用在不断变少,并且也没有使用gc。如果这是真实的、具有一定普遍性的,那么我觉得这从侧面印证了stack等内存管理机制的有效性。
至于拷贝语义,我觉得这是好的。拷贝语义实际上具有解除耦合的作用,并且在性能上不一定是无法接受的,毕竟gc也可能会进行对象的拷贝。另外拷贝语义也能提高程序访问的局部性。所以我觉得一开始大量使用拷贝语义也是可以的,在确保正确性之后再根据profile的结果来进行局部的优化,最终也可以在性能上有所确保。我觉得如果性能能够接受的话,应该优先使用拷贝语义。如果说存在一些性能之外的理由来阻止使用拷贝语义,那么我目前能够想到的只是一种情况:当需要在一个模块内直接修改另一个模块的状态时,这时因为没有接口可用,所以无法使用拷贝语义,但这个情况说明模块间的耦合度是很高的。从这个角度看,如果没有明确的性能方面的理由(时间或空间),却有大量的接口无法使用拷贝语义,那么说明设计上出了问题。

我对C#并不了解,但是我知道,即使同样使用C++编写的程序,也会有快有慢、有好有坏。当一个人从C++转向C#之后获得了收益时,可以理解为C#这个语言写出的程序的平均水平高于C++的平均水平,也可以理解为两种语言的能力的下限之间的比较,或它们的上限之间的比较,或者掌握的难易程度的比较。所以在获得更多更详细的信息之前,我觉得这样的比较很难说明什么具体的问题。

关于硬件带来的性能提高问题:有一种观点,觉得软件优化的最好手段就是什么都不做,等上18月后更换更好的硬件。这话是有道理的,但是对于用户来说,他们要花费额外的金钱;对于硬件开发人员来说,这是软件开发人员在推卸责任。现在单个CPU内核的性能提高已经非常困难,于是开始以添加内核数量的方式保持硬件性能的增长速度。但是如果软件开发人员不进行针对的设计和优化,那么单线程的任务就无法从硬件升级中获得收益,硬件开发人员再也不能当作挡箭牌了。

应用范围的确是个问题,之前的讨论一直没有明确这一点,这是个失误~ ^_^!!我个人关注的还是那些性能(时间和空间)敏感的应用。不过,我看不出stack有什么无穷的“隐患”,如果它不被误用的话……
另外,我思考这些内存管理问题的原因之一,就是看到越来越多的观点,他们认为gc对其他内存管理机制的替代性越来越高,所以我需要考虑是否应该全面拥抱gc,是否应该把gc作为最主要的内存管理手段。我个人的结论是:不行,至少目前还不行,至少目前在游戏领域内还不行。 ^_^!!
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #16 (permalink)  
旧 2008-02-17
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

原来是做游戏的啊 和我这样做网站的就不同了,让PolyRandom妖来表述一下比较好。大概网络游戏的server端稍微有点相同。

BTW, 我们做网站的换硬件用户不掏钱
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #17 (permalink)  
旧 2008-02-17
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

嗯,等其他大大来一起讨论……

那网站的硬件谁掏钱啊?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #18 (permalink)  
旧 2008-02-18
polyrandom 的头像
超级版主
 
注册日期: 2002-09-03
帖子: 3,135
文章: 20
polyrandom 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

to cat:
这里例子对C++可是不公平的,因为如果直接把C++代码写的好一点,可能也会变快很多

to sjinny:
C++代码里面我用的最多的资源管理方法,对我来说是ref counted smart pointer。这个其实也是GC的一种。相比其它GC方法来说,sp具有比较高的确定性,但是在多线程情况下也需要或多或少的同步。
至于你要求的堆栈一级的析构确定性,和GC并不冲突,你可以同时拥有两者。我不是GC的热衷爱好者,但是,我觉得大部分情况下,GC是一个plus,它没有从我手里夺去什么。大多数时候你的不爽,是语言级别的,和GC无关。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #19 (permalink)  
旧 2008-02-18
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

嗯,跟其他人讨论的时候,有的人把ref counted smart pointer当作一种gc;但也有人不认为这是gc,他们认为要采用了内存扫描技术的才能算。

ref counted smart pointer是“类gc”技术里我相对最喜欢的技术了,虽然循环引用问题让人不爽。
关于智能指针的同步问题,我想了一下,可能可以这样做:
首先,每个线程都会有个接收共享对象的接口,在这个接口里,会首先在堆上创建一个智能指针,这个智能指针指向的是传入的共享对象,然后再在桟上建立一个智能指针,它指向的是之前那个堆上的智能指针,当前线程对贡献对象的访问全部通过这个桟上的智能指针(及其副本)完成,而那个堆上的智能指针对象是不会被直接访问的,堆上的智能指针对象的地址也不会被传递给其他线程,如果要把共享对象传递给其他线程,那么就要把堆上的智能指针复制给其他线程。
class SharedObject;//共享对象
typedef SmrtPtr<SharedObject> SharedP;//一级SP
typedef SmrtPtr< SmrtPtr<SharedObject> > SharedPP;//二级SP
SharedObject和SharedP都在堆上建立实例,SharedPP只在桟上建立实例。同一个进程中,对于同一个SharedObject,一个线程里同时最多只有一个SharedP指向它。某个线程里,多个SharedPP指向一个SharedP。当一个线程对SharedObject使用完毕后,它的SharedPP都被销毁,此时线程中的SharedP被销毁。对于一个线程来说,仅仅在接收SharedObject和使用完毕后需要修改SharedObject的refCount,仅需要加锁两次。
这样每个线程仅仅加锁两次,我想大多数应用这也是能够接受的开销。空间上也只是多了一个SharedP。如果把SharedPP做成专门的类,那么使用时也能像普通的SmrtPtr一样方便,并且堆上的那个SharedP可以被完全隐藏起来。
不知道这样能不能达到“或多或少”里的那个“少”的程度。

嗯,每当谈到gc的时候我想到的第一个东西就是java,现在想起来C#里也是有桟的。不过一块内存的直接管理者只能是这两者之一,所以还是有个选择问题的。
在大多数情况下,gc会降低我对我的代码的理解程度。如果说没有gc时理解各种各样的存储管理会很费力,那么有了gc后,很多存储管理我是想看都看不到了。所以如果使用gc的话,付出的代价就是对程序掌控的减弱,当然其中还有gc与RAII难溶这一因素。
所以虽然以后C++会加入gc,但是我仍然把gc当作最后的选项。
^_^!
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #20 (permalink)  
旧 2008-02-18
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,763
Elminster 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
嗯,跟其他人讨论的时候,有的人把ref counted smart pointer当作一种gc;但也有人不认为这是gc,他们认为要采用了内存扫描技术的才能算。

ref counted smart pointer是“类gc”技术里我相对最喜