Bleeding Edge版V8引擎的内存使用评测
编者注:本文章为“搜狐NodeJS团队”的投稿,搜狐技术部在使用NodeJS方面,有着丰富的经验,本文也是他们的一份经验总结,很值得一看。原创投稿文章,版权归“搜狐技术部NodeJS团队”所有,未经允许严禁转载。
作为无阻塞事件驱动的后端框架,nodejs非常适合于处理具有海量web请求的应用。最近一段时间,我们团队一直在尝试用nodejs构建webIM服务。在使用node的过程中我们体会到了nodejs无阻塞事件驱动的优点,也遇到了一些让我们很头疼的问题,其中最让我们抓狂的问题莫过于node的内存限制以及它的GC(Garbage Collection)机制。
1.实例占用内存大小限制和GC
nodejs是基于google的v8引擎编写的。我们知道V8对单个node实例做了内存限制,在32位的系统上默认限制为512mb,在64位的系统上默认限制为1gb。对于一个tab页来说,1gb的内存显然绰绰有余,但是对于web应用来说可能并不够。node为我们提供了一个执行参数用于改变这一限制,你可以在执行node实例时加这样写”node test.js –max-old-space-size=1000 “。但是这样做仍然是有限制的,32位系统和64位系统能够设置的上限值分别是1gb和1.7gb。当然也可以采用限制接入的请求数或者开启更多实例的办法。 但是,如果你真的希望一个node实例能够占用2G以上的内存,现在的node恐怕要让你失望了。
像python、ruby此类的脚本语言一样,Node的v8引擎也是由系统进行内存回收的操作。但是程序员无法控制v8发起GC,只能标记不再使用的对象,使得下次gc回收该对象的内存空间。v8的GC采用的是stop the world的机制,在mark-sweep期间,实例会暂停运行,也就是说在某个随机的时刻所有发向该实例的请求都会被忽略掉。当你用一个node建立一个服务器监听请求,当不可控的GC发生时,你就会发现客户端只能收到502 Bad Gateway了。
2.换个发动机
在实际中,我们团队确实遇到了无法用其他方法绕过的Node内存限制以及GC机制的情况,所以我们一直在关注v8引擎在gc方面的更新。Bleeding Edge是google v8的实验性分支。最新的Bleeding Edge合并了v8的GC分支,它去除了node实例占用内存的限制并对GC的性能做了优化。对我们来说,这个版本的v8无疑具有极大的吸引力。
于是我们萌发了用新版的Bleeding edge的v8引擎升级node的想法。废话少说,看看我们是怎么给node换引擎的吧。替换v8引擎的步骤如下:
A.获取v8的bleeding版本, svn checkout http://v8.googlecode.com/svn/branches/bleeding_edge/ v8。
B.获取node-0.4.12(目前的稳定版node)
C.将node和v8的压缩包都放到服务器上,解压,将node自带的v8替换,node内置的v8位置如下:
D编译:先修改源码,因为貌似是v8抛弃(或者重命名)了string的一个枚举值。我从网上搜索这个枚举值,它对应的是0,因此我做了一个替换.
3.动力测试
更换了新引擎以后的node动力如何呢?我们拿原始版和改进版进行了的对比测试。
测试程序:
测试一:实例占用最大内存值
测试一的目标很简单,就是用程序不停申请内存,直到出现进程内存溢出,观察node的GC日志,看两个版本的node谁支持的内存更大。执行如下命令:
图中第一项scavenge和mark-sweep(感兴趣对的同学可以看一下这篇文章http://www.jiangmiao.org/blog/2247.html)是v8在进行标记回收内存,第二项的“400 –> 380 MB”这样显示是表示此次内存回收将占用内存从400MB减少到380MB,回收了20M内存,第三项“20ms”代表的是本次回收花费的时间;FATAL ERROR的出现表明内存被用尽。由于我们截的图是node实例将内存几乎耗尽的最后时刻,已经没有空闲内存能够供node进行GC,所以最后几次内存回收没有能够让占用内存量减少。
图三最后一行出现了乱码,猜测是发生了段错误,造成错误的原因可能是系统寻址的问题,232最多能表示4G的地址空间,感兴趣的童鞋可以继续研究。
结论为Bleeding Edge版node-0.4.12解除了对实例占用内存的限制,在默认配置下,内存分配可达3100MB;在指定–max-old-space-size的情况下,内存至少可以分配到4200MB。此后仅仅依靠改变–max-old-space-size参数无法继续增加内存分配,推测是寻址空间的原因,是否增加更多内存分配需要进一步的测试。
测试二:达到相同内存占用所花费的时间
测试程序同测试一,执行如下命令,记录GC花费时间:
测试二的目的是验证Bleeding Edge版node和原版node的GC性能对比。如图4所示,内存消耗为相同数量时,Bleeding Edge版的node运行时间要长,换句话说,Bleeding版由于GC性能的优化,使得GC的效率提高了。
测试三:buffer和string的内存使用对比
我们知道在bleeding edge版本出现之前,可以通过使用node的扩展数据类型buffer(http://code.google.com/p/v8/issues/detail?id=847#c8)绕过node本身堆内存限制,获得更多内存,那么Bleeding Edge版的v8引擎对string和buffer类型变量的内存回收会不会有区别呢?我们编写了如下程序尝试测试原版和改造版的node对buffer和string的GC性能。两个程序分别循环定义buffer型变量和string型变量,用node的trace-gc参数来观察内存使用情况。
程序代码:
我们把这两段代码分别用原版和Bleeding Edge版node运行。可以看到trace gc的结果如下。由于Scavenge内存回收量远远小于Mark-sweep 方式,且Scavenge持续时间非常短,所以我们在表中省略了Scavenge内存回收的数据。
由表2我们可以看出,原版的node对buffer类型和string类型变量内存回收处理性能完全一致。
由表3我们可以看出,Bleeding Edge版的node对Buffer类型和string类型变量GC效果略有区别。原生的string类型变量的GC性能略优于Buffer型变量。单位时间回收内存方面string类型相比buffer类型有2.40%的性能提升。看来尽管buffer能够绕过node的内存限制,但是它也付出了一定的性能代价。
4.结论如下:
1)Bleeding Edge确实解除了node实例的内存限制,目前通过对–max-old-space-size的设置,至少可以达到4200MB。或许对node的源码进行改写后,能够让64位系统支持给node分配超过4G的内存。
2)Bleeding Edge在GC方面有优化,在不降低计算性能的前提下,增加了内存回收的数量,提高了GC效率。
3)Bleeding Edge对string类型变量的GC性能略强于buffer类型。