最近非常喜歡用 Promise 或者 Async/Await 的異步編程方式。但最近突然發現一個問題——性能。
我直接說結論,Promise 比普通回調函數慢得多的多:
123456789101112131415161718const middle = (res) => { res(1 + 1) }console.time('Promise')for (let i = 0; i < 999999; ++i ) { new Promise(middle)}console.timeEnd('Promise') // Promise: 1768.754msconst testFn = function () { middle(function (result) { })}console.time('function')for (let i = 0; i < 999999; ++i) { testFn()}console.timeEnd('function') // function: 51.657ms
Promise 的速度相對於普通函數慢了那麼多,是否還應該繼續使用?
眾所周知,Node.js 的優勢之一就是高並發,這是 JavaScript 引擎的異步輪循機制帶來的好處。我們也知道,通常異步操作是解決 I/O 的問題(不至於整個線程卡死),上面的測試用例應該說是純計算的問題,所以測試並不完全。
那麼 I/O 問題的測試,下面來運行一段一個簡單網站(Promise 和回調函數)的測試代碼。
Promise 的測試代碼
12345678910111213141516171819202122232425// Promiseconst express = require('express')const path = require('path')// 生成隨機字符串相關const trueOrFalse = () => Math.round(Math.random()), backCode = () => 65 + Math.round(Math.random() * 25), randomChar = (lower = 0) => String.fromCharCode(backCode() + (lower && 32)), randomString = (length, lower = 0) => randomChar(lower && trueOrFalse()) + (--length ? randomString(length, lower) : '');const app = express()let count = 0;const fsp = require('fs-promise')app.use((req, res, next) => { let filepath = path.join(__dirname, '/testdir', String(count++)) fsp.writeFile(filepath, randomString(4096)) .then(() => fsp.unlink(filepath)) .catch(err => next(err)) .then(() => res.end('done: ' + count))})app.listen(3333)
普通回調函數的測試代碼
12345678910111213141516171819202122232425262728// 普通回調函數const express = require('express')const path = require('path')// 生成隨機字符串相關const trueOrFalse = () => Math.round(Math.random()), backCode = () => 65 + Math.round(Math.random() * 25), randomChar = (lower = 0) => String.fromCharCode(backCode() + (lower && 32)), randomString = (length, lower = 0) => randomChar(lower && trueOrFalse()) + (--length ? randomString(length, lower) : '');const app = express()let count = 0;const fs = require('fs')app.use((req, res, next) => { let filepath = path.join(__dirname, '/testdir', String(count++)) fs.writeFile(filepath, randomString(4096), (err, data) => { if (err) { return next(err) } fs.unlink(filepath, err => { if (err) next(err) else res.end('done: ' + count) }) })})app.listen(3333)
好的,代碼寫好了,接下來使用 ab[1] 進行測試。分別運行三次測試命令,取最後一個結果。
Promise 的測試結果
12345678910111213
H:\Software\phpStudy2016\Apache\bin>ab -n 2000 -c 2000 http://localhost:3333/(省略)Percentage of the requests served within a certain time (ms)50% 657266% 707675% 717280% 717690% 758195% 797298% 815799% 8160100% 8171 (longest request)
普通函數的測試結果
12345678910111213
H:\Software\phpStudy2016\Apache\bin>ab -n 2000 -c 2000 http://localhost:3333/(省略)Percentage of the requests served within a certain time (ms)50% 599366% 638375% 764380% 764790% 813095% 814198% 814499% 8145100% 8261 (longest request)
我們可以看到,兩者差距並不大,因為都在等待 I/O 操作,速度也快不上來。
最終結論
按照第一個測試來看,如果是純運算場景的情況下,建議少用 Promise。但其他情況的影響並不太大,所以在開發的過程中根據運算情況來權衡一下才是較好的實踐。
好像叫 Apache Benchmark ↩︎