闲扯几句 GC 的话题

标签: Go 语言 优化与技巧 | 发表时间:2011-05-11 17:17 | 作者:云风 skyan
出处:http://blog.codingnow.com/

今天跟同事闲扯的时候谈到 GAE SDK 刚刚支持了 Go 语言。这对于 Go 语言爱好者来说是个让人欢心鼓舞的消息。几乎所有人都相信它能比 Python 的执行效率高一些。从开发效率上来说,不会比 Python 差,那么 Go 语言的支持可能是比 Java 更好的选择(开发效率和执行性能的均衡)?

这也让我想到了前段在北京时跟 Douban 的同学聊 Go 的事情。那天,有同学问起 GC 的事,是个 C++ 程序员。C++ 程序员对 GC 知之甚少是可以理解的。我大约花了 10 分钟介绍简单的 GC 算法(:根扫描清理、三色标记、移动或不移动内存等等。那段时间我正在研究 lua 的 gc 实现,刚巧看了不少文章。

那天吃饭的时候,Davies 同学说到他做的 beansdb 的 proxy 用 go 实现,GC 的代价使他不得不考虑优化。我回来翻看了 Go 的代码仓库,roadmap 里,开发小组的确也有改进 GC 实现的计划。从某种意义上来说,python 的 gc 的方案不失可取之处。python 采用引用计数来立刻释放可以释放的内存,然后用扫描清理的方法来清除循环引用的死对象。这样可以减缓运行过程中的临时内存增速。甚至于,你可以在编写代码时刻意回避循环引用,像 C++ 那样管理内存。

这让我想到,其实 C/C++ 那样的手工管理内存和大多数其他现代语言支持的自动 GC 方案,其实培养的是用户(程序员)习惯。从性能上讲,各有优劣。引用计数方式并没有想象的那么廉价,扫描清理的 GC 算法也不至于拖慢系统。还有 C/C++ 惯用的有着无比性能优势的 stack 内存使用模型,stack 足够大,大到可以假设 stack 可以安全的一直使用,其实有它的局限性。像 Go 里面,把 goroutine 看成是廉价物的做法,如果按传统 C 的 stack 内存模型的话,就必须考虑 stack 的大小限制了。就算是 C/C++ 程序,老练的程序员也知道回避栈溢出。

我这几天用 C 写了一个小模块。可能早就被人无数遍造过的轮子:分析一个 path 路径字符串,划简里面的 ./ ../ 。程序最后并不长。几百行代码。各种例外让人写的很纠结,还需要设计各种测试案例来检查每种特殊情况,程序是否能正确处理。

我不由得去想,如果我用 Go 或其它现代语言会怎么干这件事情。我想,我会自动调用 strings 模块内的 split 函数,把原始字符串按 / 切分开,变成若干子串序列。然后分析其中的 . 或 .. ,把这个序列划简掉。加起来恐怕不会超过 10 行程序。

按这个思维,我完全可以用 C 实现相同的东西。也不至于纠结到在一个 buffer 上来回出来那个串。但是我在写 C 代码时没有这么做。为什么?我想是一种编码习惯吧。我在 C 程序员的角色下,想使用 O(1) 的空间,O(n) 的时间解决问题。不想分配临时对象然后最后释放它们。string 不是 first-class 类型,我无法把它当成简单的值一般使用。我在一个比较低的层面看问题,我计较每个字节内存的使用。

同样,如果身份转变为 Go 程序员,我会把那些负担转嫁给 gc 给编译器,祈祷他们可以做的很好。无形中,我的代码临时分配了许多对象,把数据复制转移到低层次的模块(strings 模块)去处理。其实,在 Go 里,你还是完全可以采用 C 语言中同样的算法解决问题。

回到 Davies 的问题,我想,如果仔细推敲的话,或许可以不用 unsafe 模块中的方法去直接调用 malloc/free 。做一个 buffer 池,应该也能让程序的内存空间不至于暴涨。我们用 lua/python/go 这些内建 GC 的语言编写程序时,有心留意,总可以让临时对象不至于增加的太快,这样就能减少 GC 的负担。但是少有项目做的到。因为语言给你的思考方式决定了你怎样编写程序。

有另一个有意思的比较:

C++ 的 STL 看起来是很高效的,如果你仔细阅读过 STL 的源代码,你更会同意这点。可是,少有 C++ 程序员肯承认,使用 STL 拖慢了他们的程序。真正的 C++ 程序员鄙视那些对 STL 拙劣的模仿,他们叫嚣,要使用 std::vector ,不要重造轮子,少用 C 风格的数组。还有 std::algorithm 里的那些东西……

若干年前我考察过我经历的两个功能类似的项目,一个是用 C 风格的 C++ 写的,一个是用 STL 风格的 C++ 写的。hook 内存管理器我能发现,C 风格的项目中内存分配的频率远远少于 STL 风格的项目。大约只有 1/3 左右。从那时起,我相信,C++ 项目普遍会比 C 项目稍慢一点。根源不在于语言编译器生成的目标码的区别,在于语言带给程序员的思考方式。所以也不必迷信那些语言性能评测报告。那些精心优化过的短小代码说明不了实战中的问题。

相关 [gc] 推荐:

Java GC 调优

- - Darktea
关于 Java GC 已经有很多好的文档了, 比如这些:. 但是这里还是想再重点整理一下 Java GC 日志的格式, 可以作为实战时的备忘录.. 同时也会再整理一下各种概念. 一, JDK 6 提供的各种垃圾收集器. 先整理一下各种垃圾收集器.. 新生代收集器: Serial, ParNew, Parallel Scavenge (MaxGCPauseMillis vs.

[译]GC专家系列3-GC调优

- - SegmentFault 最新的文章
原文链接: http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collection/. 本篇是”GC专家系列“的第三篇. 在第一篇 理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.

GC 日志分析

- - 码蜂笔记
不同的JVM及其选项会输出不同的日志. 生成下面日志使用的选项: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:d:/GClogs/tomcat6-gc.log. 最前面的数字 4.231 和 4.445 代表虚拟机启动以来的秒数.

初级分代GC

- - C++博客-首页原创精华区
通常情况下GC分为两种,分别是:扫描GC(Tracing GC)和引用计数GC(Reference counting GC). 其中扫描GC是比较常用的GC实现方法,其原理是:把正在使用的对象找出来,然后把未被使用的对象释放. 而引用计数GC则是对每个对象都添加一个计数器,引用增加一个计数器就加一,引用减少一个计数器就减一,当计数器减至零时,把对象回收释放.

一个GC频繁的Case

- loudly - BlueDavy之技术Blog
前两天碰到一个很诡异的GC频繁的现象,走了不少弯路,N种方法查找后才终于查明原因了,在这篇blog中记录下,以便以后碰到这类问题时能更快的解决. 前两天一位同学找到我,说有个应用在启动后就一直Full GC,拿到GC log先看了下,确实是非常的诡异,截取的部分log如下:. 这个日志中诡异的地方在于每次Full GC的时候旧生代都还有很多的空间,于是去看来下启动参数,此时的启动参数如下:.

Java GC日志查看

- - Java - 编程语言 - ITeye博客
Java中的GC有哪几种类型. 虚拟机运行在Client模式的默认值,打开此开关参数后,. 使用Serial+Serial Old收集器组合进行垃圾收集. 打开此开关参数后,使用ParNew+Serial Old收集器组合进行垃圾收集. 打开此开关参数后,使用ParNew+CMS+Serial Old收集器组合进行垃圾收集.

GC的基本算法

- - 非技术 - ITeye博客
1、引用计数(reference counting).     原理:此对象有一个引用,则+1;删除一个引用,则-1. 缺点:无法处理循环引用的问题. 对象A和B分别有字段b、a,令A.b=B和B.a=A,除此之外这2个对象再无任何引用,那实际上这2个对象已经不可能再被访问,但是引用计数算法却无法回收他们.

CMS gc实践总结

- - 编程语言 - ITeye博客
声明:原文转自http://www.blogjava.net/killme2008/archive/2009/09/22/295931.html,该文所有合法权益归原作者所有,仅在此做技术分享使用. 首先感谢阿宝同学的帮助,我才对这个gc算法的调整有了一定的认识,而不是停留在过去仅仅了解的阶段. 在读过sun的文档和跟阿宝讨论之后,做个小小的总结.

面向GC的Java编程

- - 并发编程网 - ifeve.com
Java程序员在编码过程中通常不需要考虑内存问题,JVM经过高度优化的GC机制大部分情况下都能够很好地处理堆(Heap)的清理问题. 以至于许多Java程序员认为,我只需要关心何时创建对象,而回收对象,就交给GC来做吧. 甚至有人说,如果在编程过程中频繁考虑内存问题,是一种退化,这些事情应该交给编译器,交给虚拟机来解决.

Java GC 调试手记

- - 非技术 - ITeye博客
本文记录GC调试的一次实验过程和结果. 问题1:为什么要调试GC参数. 在32核处理器的系统上,10%的GC时间导致75%的吞吐量损失. 所以在大型系统上,调试GC是以小博大的不错选择. 问题2:怎么样调试GC?. 调试GC,有 三个主要的参数:. 选择合适的GC Collector. 整个JVM Heap堆的大小.