Node.js V0.12 新特性之性能优化

  • 2014-04-01
  • 本文字数:1887 字

    阅读完需:约 6 分钟

v0.12 悠长的开发周期 (已经过去九个月了,并且还在继续,是有史以来最长的一次) 让核心团队和贡献者们有充分的机会对性能做一些优化。本文会介绍其中最值得注意的几个。

支持塞住模式的可写流

现在可写流可以支持“塞住(corked)”模式,类似于你执行 man tcp时见到的 socket 选项TCP_CORKTCP_NOPUSH

当被塞住时,写到流中的数据会排队直到流被重新开塞 (uncorked)。这样 Node.js 可以将比较小的写操作合并成比较大的,从而减少系统调用和 TCP 往返。

http 模块已经升级了,在发送分块的请求或响应主体时透明地使用塞住模式。如果你经常看看 strace(1) 输出,就会发现 writev(2) 系统调用更多了,而 write(2) 变少了。

提升 TLS 性能

tls 模块在 Node.js v0.12 中做了相当大的重构。

在 Node.js v0.10 中,tls 模块在 net 模块之上,作为对网络流量透明地加密和解密的传输流。从工程角度来看,这样分层是有必要的,但会导致开销 - 更多的内存转移以及在 V8 VM 中超出绝对必要的调入调出–并妨碍优化。

所以在 node.js v0.12 中,直接用 libuv 重写了 tls 模块。现在它直接把接入的网络流量拉出来加密,无需通过中间层。

尽管这种评测不太科学,但用空密码时表明现在 TLS 一般要快 10%,并且用的内存更少。(我得说减少的内存占用可能部分是因为重构的内存管理,这是 v0.12 的另一项优化。)

(还有,如果你想知道,空密码时不会对负载加密的密码;它们可以用来测量架构和协议的开销。)

对于最终用户来说,tls 模块的大部分变化都是透明的。其中最显眼的一个是现在 TLS 连接是从 tls.TLSSocket,而不是 tls.CryptoStream中得到的。

提升 Crypto 性能

几种加解密算法现在应该更快了,有时是快 _ 很多 _。先介绍下背景:

Node.js 中的加解密使用 OpenSSL 库实现的。OpenSSL 中的算法有用 C 编写,并针对各种平台和架构的手动程序集版本。

Node.js v0.10 已经给一些东西用上了程序集版本,而 v0.12 又大范围扩充了这一范围。此外,现在在有 CPU 支持时用上了 AES-NI ,过去三四年生产的大多数 x86 处理器都支持。

在 Linux 上,如果执行grep ^flags /proc/cpuinfo | grep -w aes找到任何匹配结果,那就说明你的系统支持 AES-NI。不过像 VMWare 或 VirtualBox 之类的虚拟机管理程序可能会对客户机操作系统隐藏 CPU 的真正能力,包括 AES-NI。

启用 AES-NI 有个有趣的结果,即像 AES128-GCM-SHA256 这种工业强度的加解密现在比 NULL-MD5 这样的非加密密码还要快很多!

减少垃圾收集的应激反应

多情景重构的一个副作用是极大减少了Node.js 核心中的持久句柄的数量。

持久句柄是对V8 堆上对象的强引用,防止对象在引用再次移除之前被垃圾收集器回收(按GC 的说法,它是一个人造的GC 根。)

Node.js 用持久句柄缓存经常使用的值,比如 strings 或 object 原型。然而持久句柄在垃圾收集器中需要一个特殊的后处理步骤,并且根据句柄数量有一个线性增长的开销。

因为多情景清理的作用,大部分持久句柄或者被消掉,或者切换成一个更轻量的机制(被称为’永恒句柄’;这个名字在暗示什么呢?)

最终效果是你的程序在垃圾收集器内部所用时间更少了,把更多的时间用在了实际的工作上。现在在node –prof的输出中v8::internal::GlobalHandles::PostGarbageCollectionProcessing()出现的次数应该少了很多。

更好的集群性能

Node.js v0.10 的 cluster 模块要靠操作系统给工人进程均匀地分发接入连接。

事实证明,在 Solaris 和 Linux 上,有些工作负载在工人进程之间分散地非常不均衡。为了缓解这种状况,Node.js v0.12 已经改了,默认使用轮转法。这篇文章中有更详细的介绍。

更快的计时器, 更快的 setImmediate(), 更快的process.nextTick()

setTimeout()和它的朋友们现在用的时间源不仅更快,而且对时钟偏移免疫。这一优化在所有平台上都启用了,但在 Linux 上我们更进一步,直接从 VDSO 上读取当前时间,因此极大降低了 gettimeofday(2) clock_gettime(2)系统调用的次数。

setImmediate()process.nextTick()也给通常情况下的派发添加了快速的路径,可以见到性能上的调整。也就是说虽然这些函数已经相当快了,但它们现在更快了。

作者简介

本文最初由 Ben Noordhuis 发表在 StrongLoop 上。Ben Noordhuis 从 2010 年就跟着 Ryan Dahl 开发 Node.js 的核心代码。他一直在为改进 Node 核心代码而努力做着编码、调试和基准测试等工作。作为最高产的 Node 核心开发者之一,Ben 编写了 Node.js 和 libuv 中的很多代码。 StrongLoop 降低了在 Node 中开发APIs 的难度,还添加了监测、集群化以及私有注册的支持等DevOps 能力。

查看英文原文 What’s New in Node.js v0.12 – Performance Optimizations

2014 年 1 月 21 日