Node.js 8.0.0 async pattern benchmark

Node.js 的 8.0 版本终于发布了,等了两天没人测评,于是决定抛砖引玉。
首先需要说明的是,由于不方便拿业务代码测试来评测,所以本次基准测试(benchmark)毫无新意的沿用了 bluebird 的基准测试。测试比较的的内容是多个异步模式(async pattern)之间的性能对比。

目前 Node.js 业内比较异步模式性能比较靠谱的方式是基于 Gorki Kosev’s 的 《Analysis of generators and other async patterns in node》 的 benchmark。该 benchmark 模拟会模拟同时混合大量同步与异步操作的处理场景。具体指标是处理这些操作消耗的时间(ms)以及内存(MB)。

直接上一个 Node.js v8.0.0 的测试报告:

results for 10000 parallel executions, 1 ms per I/O op

file                                       time(ms)  memory(MB)
callbacks-baseline.js                           328       24.11
callbacks-suguru03-neo-async-waterfall.js       409       35.66
promises-bluebird-generator.js                  703       40.76
promises-bluebird.js                            781       51.74
promises-then-promise.js                        922       67.02
promises-cujojs-when.js                         963       65.11
promises-lvivski-davy.js                       1071       96.27
promises-tildeio-rsvp.js                       1159       87.05
callbacks-caolan-async-waterfall.js            1345      102.79
promises-dfilatov-vow.js                       1504      131.96
promises-calvinmetcalf-lie.js                  1870      164.99
promises-ecmascript6-native.js                 2167      167.91
promises-obvious-kew.js                        2269      225.37
generators-tj-co.js                            2273      119.98
observables-pozadi-kefir.js                    3170      188.55
promises-medikoo-deferred.js                   3202      114.25
observables-Reactive-Extensions-RxJS.js        4622      238.07
streamline-generators.js                       6096      128.54
promises-kriskowal-q.js                       12051      391.93
observables-caolan-highland.js                12269      534.94
streamline-callbacks.js                       62359      198.94
observables-baconjs-bacon.js.js                 OOM         OOM

Platform info:
Linux 2.6.32-042stab117.14 x64
Node.JS 7.7.4
V8 5.5.372.42
Intel(R) Xeon(R) CPU           L5640  @ 2.27GHz × 2

其中的测试项中, 比较常见的详情分别是:

执行速度最快的还是原生的 callback

在内存占用方面可以看到 native 的 promise 较之前的版本有明显的下降,已经达到 async 模块的水平了。

完整执行数据 (ms)

pattern 6-1 6-2 7-1 7-2 8-1 8-2
callbacks-baseline.js 335 348 345 328 357 370
callbacks-suguru03-neo-async-waterfall.js 426 433 428 409 462 428
promises-bluebird.js 829 892 750 781 779 770
promises-bluebird-generator.js 838 973 707 703 782 789
promises-cujojs-when.js 916 961 978 963 977 965
promises-then-promise.js 827 913 940 922 987 1102
promises-lvivski-davy.js 1014 1149 1035 1071 1131 1142
promises-tildeio-rsvp.js 1085 1124 1189 1159 1186 1142
promises-ecmascript6-native.js 2530 2540 2286 2167 1216 1350
callbacks-caolan-async-waterfall.js 1317 1312 1427 1345 1433 1430
generators-tj-co.js 2753 2642 2267 2273 1616 1671
promises-dfilatov-vow.js 1382 1496 1665 1504 1688 1770
promises-calvinmetcalf-lie.js 1650 1795 1784 1870 2031 2177
promises-obvious-kew.js 2326 2421 2173 2269 2610 2667
promises-medikoo-deferred.js 4231 4416 3477 3202 3227 3048
observables-pozadi-kefir.js 60466 64587 3177 3170 3442 3161
streamline-generators.js 5533 6048 6682 6096 3013 3228
streamline-callbacks.js 8340 8784 79046 62359 4360 4339
observables-Reactive-Extensions-RxJS.js 4488 4947 4514 4622 5614 5578
observables-caolan-highland.js 152903 164368 12903 12269 17796 17243
promises-kriskowal-q.js 14618 15963 13198 12051 21159 19219
observables-baconjs-bacon.js.js 37014 33282 45257

完整内存数据 (MB)

pattern 6-1 6-2 7-1 7-2 8-1 8-2
callbacks-baseline.js 30 30 24 24 31 31
callbacks-suguru03-neo-async-waterfall.js 43 43 35 35 39 39
promises-bluebird-generator.js 39 40 40 40 41 43
promises-bluebird.js 49 49 50 51 49 48
promises-cujojs-when.js 59 60 65 65 61 60
promises-then-promise.js 59 59 68 67 73 74
generators-tj-co.js 131 131 119 119 74 75
promises-tildeio-rsvp.js 89 89 88 87 80 82
promises-lvivski-davy.js 91 90 97 96 103 102
streamline-generators.js 179 179 129 128 102 102
promises-ecmascript6-native.js 187 187 163 167 95 104
callbacks-caolan-async-waterfall.js 101 101 102 102 108 108
streamline-callbacks.js 163 251 199 198 127 126
promises-dfilatov-vow.js 130 147 132 131 153 159
promises-calvinmetcalf-lie.js 152 135 164 164 147 166
promises-medikoo-deferred.js 190 190 114 114 183 183
observables-pozadi-kefir.js 146 152 188 188 202 202
promises-obvious-kew.js 217 216 79 225 228 232
observables-Reactive-Extensions-RxJS.js 229 230 236 238 243 244
promises-kriskowal-q.js 837 848 370 391 387 388
observables-caolan-highland.js 485 492 517 534 585 559
observables-baconjs-bacon.js.js 826 833 582

小结

  • neo 版的 async 依旧是最快的异步解决方案。
  • 依赖 native 版本的实现(promise/co 等)都有一定程度的性能、内存提升(不过提升的不是很多)。
  • native 的 promise 在提升之后性能已与 async 模块相当,并且 bluebird 的实现与 native 版本差距进一步缩小,bluebird 已经没有成倍的优势。

PS:目前该 benchmark 尚未加入 async/await (笔者已经提了 issue,在考虑要不要自己撸),加入之后会更新本文。

http://echarts.min.js

Advertisements

Mac Sublime 的 Switch Project 不能输入

这个坑爹 BUG, 起因是启用了搜狗输入法的 [中文英文间加入空格], 于是搜狗拦截输入, 使得在 Sublime 的部分地方出现了不能输入的情况.

引申一下, Sublime 对跨平台的输入源处理的不是很好, 之前也出现过按住回车不能一直换行, 按住不动只会插入一个换行, 那时切换搜狗输入法就好了.

Mac docker-machine: Error with pre-create check: “exit status 126”

126 是没有安装 VirtualBox 的报错. 如果你想安装是可以通过如下命令安装:

if ! type "brew" > /dev/null; then
  ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)";
fi
brew tap phinze/homebrew-cask && brew install brew-cask;
brew cask install vagrant;
brew cask install virtualbox;

不过需要了解的是 Docker for Mac 默认是没有使用 VirtualBox 而是使用了 HyperKit, 所以是不用建立 VirtualBox 来做 service. 如果你需要改什么配置可以直接通过 docker for Mac 的 Docker -> Preferences 去改.

ubuntu vps 安装 docker 失败 Cannot connect to the Docker daemon at unix:///var/run/docker.sock.

详情见兴致冲冲的安装:

wget -qO- https://get.docker.com/ | sh

然后

docker info

输出:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

通过 service docker restart 尝试没有变化, 查看 docker 的日志 tail -5f /var/log/upstart/docker.log 发现

time="2017-04-23T01:13:59.857654612-04:00" level=fatal msg="Your Linux kernel version 2.6.32-042stab116.2 is not supported for running docker. Please upgrade your kernel to 3.10.0 or newer."

所以问题是, 当前 linux 的 kernel 2.6 版本太低, 需要升级到 3.10 以上. 坑爹的 vps 自带模板[捂脸] 然后基于 OpenZV 6 的 vps 默认内核是 2.6, docker 需要基于 3.x 的内核, 只有 OpenZV 7 的 vps 才支持 (基于 3.x 内核).

Node.js 的 require.resolve 简介

简单的说, 在 Node.js 中使用 fs 读取文件的时候, 经常碰到要拼一个文件的绝对路径的问题 (fs 处理相对路径均以进程执行目录为准). 之前一直的方法都是, 使用 path 模块以及 __dirname 变量 :

fs.readFileSync(path.join(__dirname, './assets/some-file.txt'));

使用 require.resolve 可以简化这一过程:

fs.readFileSync(require.resolve('./assets/some-file.txt'));

此外, require.resolve 还会在拼接好路径之后检查该路径是否存在, 如果 resolve 的目标路径不存在, 就会抛出 Cannot find module './some-file.txt' 的异常. 省略了一道检查文件是否存在的工序 (fs.exists).

这个报错并不会加重你的检查负担, 毕竟使用 fs 去操作文件时, 如果发现文件不存在也会抛出异常. 反之, 通过 require.resovle 可以在提前在文件中作为常量定义, 那么在应用启动时就可以抛异常, 而不是等到具体操作文件的时候才抛异常.

牛顿二项式与 e 级数

复习一下数学, 找一下回忆.

先是从二项式平方开始:

(a+b)^{2} = a^{2} + 2ab + b^2

其实展开是这样的:

(a+b)^{2} = aa + ab + ba + bb

再看立方:

(a+b)^{3} = a^3 + 3a^{2}b + 3ab^2 + b^3
(a+b)^{3} = aaa + aab + aba + baa + bba + bab + abb + bbb

通过排列组合的方式标记, 于是:

(a+b)^3 = {3 \choose 0}a^3 b^0 + {3 \choose 1} a^{2}b^1 + {3 \choose 2} a^{1}b^2 + {3 \choose 3} a^{0} b^{3}

通过数学归纳法可以拓展:

(a+b)^n = {n \choose 0}a^n b^0 + {n \choose 1}a^{n-1}b^1 + {n \choose 2}a^{n-2}b^2 + \cdots + {n \choose n-1}a^1 b^{n-1} + {n \choose n}a^0 b^n

使用求和简写可得:

(a+b)^n = \sum_{k=0}^n {n \choose k}a^{n-k}b^k = \sum_{k=0}^n {n \choose k}a^{k}b^{n-k}

e 级数

数学常数 e (The Constant e – NDE/NDT Resource Center) 的定义爲下列极限值:

e = \lim_{n\to\infty} \left(1 + \frac{1}{n}\right)^n.

使用二项式定理能得出

\left(1 + \frac{1}{n}\right)^n = 1 + {n \choose 1}\frac{1}{n} + {n \choose 2}\frac{1}{n^2} + {n \choose 3}\frac{1}{n^3} + \cdots + {n \choose n}\frac{1}{n^n}.

第 k 项之总和为

{n \choose k}\frac{1}{n^k} \;=\; \frac{1}{k!}\cdot\frac{n(n-1)(n-2)\cdots (n-k+1)}{n^k}

因为 n → ∞,右边的表达式趋近1。 因此

\lim_{n\to\infty} {n \choose k}\frac{1}{n^k} = \frac{1}{k!}.

由于序列的极限可以相加, 所以 e 可以表示为:

e = \sum_{k=0}^\infty\frac{1}{k!}=\frac{1}{0!} + \frac{1}{1!} + \frac{1}{2!} + \frac{1}{3!} + \cdots.

计算情况:

e = 1/0! + 1/1! + 1/2! + 1/3! + 1/4! + …

As an example, here is the computation of e to 22 decimal places:

1/0! = 1/1 = 1.0000000000000000000000000
1/1! = 1/1 = 1.0000000000000000000000000
1/2! = 1/2 = 0.5000000000000000000000000
1/3! = 1/6 = 0.1666666666666666666666667
1/4! = 1/24 = 0.0416666666666666666666667
1/5! = 1/120 = 0.0083333333333333333333333
1/6! = 1/720 = 0.0013888888888888888888889
1/7! = 1/5040 = 0.0001984126984126984126984
1/8! = 1/40320 = 0.0000248015873015873015873
1/9! = 1/362880 = 0.0000027557319223985890653
1/10! = 1/3628800 = 0.0000002755731922398589065
1/11! = 1/39916800 = 0.0000000250521083854417188
1/12! = 1/479001600 = 0.0000000020876756987868099
1/13! = 1/6227020800 = 0.0000000001605904383682161
1/14! = 1/87178291200 = 0.0000000000114707455977297
1/15! = 1/1307674368000 = 0.0000000000007647163731820
1/16! = 1/20922789887989 = 0.0000000000000477947733239
1/17! = 1/355687428101759 = 0.0000000000000028114572543
1/18! = 1/6402373705148490 = 0.0000000000000001561920697
1/19! = 1/121645101098757000 = 0.0000000000000000082206352
1/20! = 1/2432901785214670000 = 0.0000000000000000004110318
1/21! = 1/51091049359062800000 = 0.0000000000000000000195729
1/22! = 1/1123974373384290000000 = 0.0000000000000000000008897
1/23! = 1/25839793281653700000000 = 0.0000000000000000000000387
1/24! = 1/625000000000000000000000 = 0.0000000000000000000000016
1/25! = 1/10000000000000000000000000 = 0.0000000000000000000000001

For more information on e, visit the the math forum at mathforum.org
The sum of the values in the right column is 2.7182818284590452353602875 which is “e.”

Reference: The mathforum.org

Node.js Mongodb 密码特殊字符 @

在去年的 DB 勒索事件之后, 不少的同学开始加强 Mongodb 的安全性, 其中一种办法就是设置复杂的密码. 那么问题来了, 如果设置的密码里包含一些如 “@”, “:” 一样的特殊字符怎么办?

mongodb://username:password@host:port/db

这种情况可能使得你的 Mongodb 连接串不能被正常解析, 并且完全有可能出现. 烦人的地方在于:

  1. 使用 “” 双引号将 password 包起来没有用
  2. 使用 \@ 转义也没有用

解决方案1

开启 uri_decode_auth 功能, 拼接连接串之后先 encode 一下, 然后通过 uri_decode_auth 在 driver 内部 decode 来绕过这个问题

mongoClient.connect("mongodb://username:p%40ssword@host:port/dbname", {
    uri_decode_auth: true
    }, function(err, db) {

    }
);

更新 Driver 2.2.25 is a bugfix release and contains the following fixes.
2.2.25 2017-03-17

之后的版本已经不需要显示指定 uri_decode_auth: true, 直接 encode 即可。

解决方案2

 

老老实实查文档, 在 options 中指明:

mongoose.connect('mongodb://localhost/test',
                 {user: 'username', pass: 'p@ssword'},
                 callback);