Nunjucks服務端渲染性能測試


手把手教你Egg/Vue/Nunjucks服務端渲染性能測試


在用Vue做服務端渲染時,大家對Vue服務端渲染的性能持懷疑態度,業界也有一些嘗試,不過完整的產品項目和數據分析比較少。結合超級遊戲中心,我們對 Vue 和 Nunjucks 進行模板渲染, 針對Render時間,CPU佔用,內存佔用進行全面的對比測試。

渲染性能對比

通過實現相同的模板功能,分別針對無緩存和有緩存進行了對比測試. 測試方法,通過ab壓測工具對頁面進行測試,Node層收集頁面render渲染時間, 然後進行彙總統計分析。

Nunjucks 測試模板

<code>


h5GameList



    {% for item in h5GameList.data.list %}

  • {% endfor %}




recommendList


{% for item in recommendList.data %}


{{item.recommendResume}}



{% endfor %}



bestList


{% for item in bestList.data.list %}


{{item.packageName}}



{% endfor %}


bookingList


{% for item in bookingList.data %}


{{item.resume}}



{% endfor %}


/<code>

Vue測試模板

<code><template>


h5GameList







recommendList




{{item.recommendResume}}






bestList




{{item.packageName}}





bookingList




{{item.resume}}





/<template>
/<code>

測試腳本一:

ab -c 50 -n 1000 http://ip:port/perf/nunjucks/ ab -c 50 -n 1000 http://ip:port/perf/vue/ 其中 -n 表示請求數,-c 表示併發數

手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

ab-1000.png

測試腳本二:

ab -c 50 -n 5000 http://ip:port/perf/nunjucks/ ab -c 50 -n 5000 http://ip:port/perf/vue/

手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

ab-5000.png

從上面統計來看可以得出如下結論:

  • 無緩存情況:nunjucks 平均時間比 vue 渲染要快1ms, 差距沒想象的大。
  • 有緩存情況:nunjucks 平均時間是 vue 的3倍左右,但也都是毫秒級別的差異。在整理數據時,nunjucks的render時間都是0-1ms,而vue則要1-3ms。分析一下可知道,nunjucks是基於字符串正則編譯的,可以做到預編譯緩存,而vue目前的實現方式是通過webpack構建的jsbundle文件(查看構建文件可以看到每個元素和組件都是通過javascript動態創建組合的),然後整個script丟給Node.js 原生vm模塊(獨立的沙箱運行空間)動態執行script。 目前Vue官方提供了基於Webpack構建Vue字符串的方案,宣傳性能會有比較大的提升。這種方案前一段時間做個實踐,每一個頁面都需要獨立構建一個manifest和vue-ssr-server-bundle.json 文件, 比較適合單頁面應用,多頁面應用構建有衝突,需要自己實現支持,待實踐。從遊戲中心實踐來看, render時間在整個耗時鏈條裡面是非常小的,性能問題大頭部分在於網絡( DNS,網絡連接,網絡傳輸),接口耗時兩部分。
  • vm對比測試
<code>global.num = 100; 

const vm = require('vm');
const code = 'var ret = num * num * num;';
const sandbox = { num : 1000};
const benchmark = (msg, fun) => {
const start = new Date;
for (let i = 0; i < 10000; i++) {
fun();
}
const end = new Date;
console.log(msg + ': ' + (end - start) + 'ms');
};

const ctx = vm.createContext(sandbox);


// runInThisContext 用於創建一個獨立的沙箱運行空間,code內的代碼可以訪問外部的global對象,但是不能訪問其他變量
benchmark('vm.runInThisContext', () => {
vm.runInThisContext(code);
});

// runInContext 創建一個獨立的沙箱運行空間,sandBox將做為global的變量傳入code內,但不存在global變量
benchmark('vm.runInContext', () => {
vm.runInContext(code, ctx);
});

// 與runInContext 一樣, 這裡可以直接傳sandbox
benchmark('vm.runInNewContext', () => {
vm.runInNewContext(code, sandbox);
});

const/>
benchmark('script.runInThisContext', () => {
/>});
benchmark('script.runInNewContext', () => {
/>});
benchmark('script.runInContext', () => {
/>});
benchmark('fn', () => {
new Function('num', code)();
});

/*
vm.runInThisContext: 15ms
vm.runInContext: 71ms
vm.runInNewContext: 70ms
/>/>/> fn: 9ms

/> */
/<code>

線上應用性能數據


首頁內容有5-6屏內容,一次性渲染,部分組件動態加載。

首頁模板編譯時間

手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

vue-render.png

從首頁render-avg的統計來看,== 模板的編譯時間非常的短,平均在24ms-27ms之間==,還有優化的空間。

首頁訪問鏈路時間

手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

vue-perf.png

從整個鏈路時間來看rt(首屏時間) 可以看到, 平均首屏時間小於1s, 而render時間平均在30ms,在整個鏈路上面,==render的時間可以說是可以忽略的==,至少從上面圖來看,==性能問題大頭部分在於網絡,接口耗時兩部分。==

CPU和內存佔用

前提條件:

  • Mac 環境
  • Nunjucks 和 Vue 渲染都開啟緩存
  • Vue 服務端渲染關閉 runInNewContext
  • 為保證測試的統計準確性,只啟動一個工作worker,下面分析只統計 worker進程CPU和內存,排除了 Egg master 和 agent 進程。

Nunjucks CPU和內存佔用

  • 採集樣本:ab -c 100 -n 50000 http://ip:port/perf/nunjucks/
<code>This is ApacheBench, Version 2.3 
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 100.84.250.56 (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests

Completed 20000 requests
Completed 25000 requests
Completed 30000 requests
Completed 35000 requests
Completed 40000 requests
Completed 45000 requests
Completed 50000 requests
Finished 50000 requests


Server Software:
Server Hostname: 100.84.250.56
Server Port: 7001

Document Path: /perf/nunjucks/
Document Length: 13899 bytes

Concurrency Level: 100
Time taken for tests: 173.686 seconds
Complete requests: 50000
Failed requests: 48138
(Connect: 0, Receive: 0, Length: 48138, Exceptions: 0)
Total transferred: 709284995 bytes
HTML transferred: 694684887 bytes
Requests per second: 287.88 [#/sec] (mean)
Time per request: 347.372 [ms] (mean)
Time per request: 3.474 [ms] (mean, across all concurrent requests)
Transfer rate: 3988.00 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 10 38.1 2 3410
Processing: 22 336 313.5 241 3416
Waiting: 22 333 302.2 241 3398
Total: 56 347 311.6 247 3432

Percentage of the requests served within a certain time (ms)
50% 247
66% 280
75% 332
80% 367
90% 645
95% 877
98% 1195
99% 1460
100% 3432 (longest request)
/<code>
手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

nunjucks-cpu-memory.png

Vue CPU和內存佔用

  • 採集樣本:ab -c 100 -n 50000 http://ip:port/perf/vue/
<code>This is ApacheBench, Version 2.3 
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 100.84.250.56 (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Completed 20000 requests
Completed 25000 requests
Completed 30000 requests
Completed 35000 requests
Completed 40000 requests
Completed 45000 requests
Completed 50000 requests
Finished 50000 requests


Server Software:
Server Hostname: 100.84.250.56
Server Port: 7001

Document Path: /perf/vue/
Document Length: 13840 bytes

Concurrency Level: 100
Time taken for tests: 193.524 seconds
Complete requests: 50000
Failed requests: 48989
(Connect: 0, Receive: 0, Length: 48989, Exceptions: 0)
Total transferred: 707135621 bytes
HTML transferred: 692535158 bytes
Requests per second: 258.37 [#/sec] (mean)
Time per request: 387.048 [ms] (mean)
Time per request: 3.870 [ms] (mean, across all concurrent requests)
Transfer rate: 3568.35 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max

Connect: 0 10 27.3 2 1384
Processing: 22 377 223.9 285 2236
Waiting: 22 373 217.7 285 2235
Total: 42 386 219.5 290 2239

Percentage of the requests served within a certain time (ms)
50% 290
66% 335
75% 409
80% 481
90% 697
95% 841
98% 1030
99% 1126
100% 2239 (longest request)dou
/<code>
手把手教你Egg/Vue/Nunjucks服務端渲染性能測試

vue-cpu-memory.png

兩個圖對比發現如下信息:

  • 壓測前 egg 應用啟動後,worker 進程內存穩定在 60MB, cpu 使用都小於1%,保證前提條件一致
  • 壓測啟動後,nunjucks 和 vue CPU使用迅速飆升到90%,曲線基本保持一樣,沒有很明顯高低之分
  • 壓測啟動後,nunjucks 和 vue 內存也是迅速上升,整個壓測期間,nunjucks 平均內存使用為 150 MB左右,vue 平均內存使用為 160 MB, vue 內存佔用比較穩定。從這個數據可以看出, Vue 服務端渲染 內存佔用略微比 nunjucks 高一些。
  • 壓測結束好後,nunjucks 和 vue 的 CPU 使用 迅速降為小於 1%, 內存使用迅速降為 60 MB, 都恢復為壓測前的狀態,這也表面 nunjucks 和 vue 壓測期間沒有出現內存洩漏的情況。

Nunjucks與Vue對比分析

首先我們來看看 ab 執行結果的幾個關鍵參數含義:

  • Concurrency Level 併發請求數
  • Time taken for tests 整個測試持續的時間
  • Complete requests 完成的請求數
  • Failed requests 失敗的請求數(指內容
    大小不一樣,其實是成功的)
  • HTML transferred HTML內容傳輸量
  • Requests per second 每秒處理的請求數,mean表示平均值
  • Time per request 用戶平均請求等待時間
  • Time per request(mean, across all concurrent requests) 服務器平均請求處理時間
  • Percentage Time 處理時間區間分佈,我們關注80%-90%的區間。

ab 對比數據可以得出以下結論:

  • HTML transferred 內容傳輸量數據非常接近,保證了對比測試的客觀性。
  • 50000個請求,vue 整體處理時間比 nunjucks 慢 20s, 平均每個相當於慢 0.4 ms,這個於 上面 render 數據對比是吻合的。
  • nunjucks 每秒處理的請求數比 vue 略微多 30 個, 用戶平均請求等待時間少 0.4 ms
  • 從 percentage time 時間我們發現 nunjucks 和 vue 每個區間都是非常接近。

總體上,nunjucks 和 vue 在 模板渲染,CPU使用,內存佔用沒有很明顯的差異,各指標基本接近。 其中 nunjucks 在模板渲染方面略微快一點點(個位數毫秒級), 內存佔用方面 vue 比 nunjucks 佔用略微多一點,但都在可接受範圍內。

CPU和內存工具

在進行 CPU 和 內存 監控統計分析時,也沒有找到簡單好用的火焰圖工具。Alinode 平臺統計粒度太大,數據也不是時時可以拿到,也不好使。找到一些成熟的工具比如 perf 和 FlameGraph 都比較複雜,而且一些資料都是 linux 上面的, 配置相當繁瑣,只好放棄。另外找到 Mac 的一個小工具 iStat Menus 能顯示電腦磁盤CPU,內存等佔用情況不錯,圖也很小且不適合做具體分析,作為電腦監控工具還不錯。最終也沒有找到合適簡單工具,只好簡單擼一個,順便玩了一把 socket.io 和圖表工具。上面 CPU 和 內存 統計是通過 Egg egg-socket.io 和 egg-schedule 插件, current-processes, socket.io.js 以及圖片庫 Ignite UI 實現的。

  • Egg Node端 egg-socket.io和egg-schedule current-processes 結合
<code>'use strict';
const ps = require('current-processes');
const os = require('os');
const totalMem = os.totalmem(); // bytes to MB

module.exports = app => {

exports.schedule = {
interval: '3s',
type: 'worker'
};

exports.task = function* (ctx) {
ps.get((err, processes) => {
const proArr = processes.filter(pro => {
return pro.name === 'node';
}).sort((a, b) => {
return a.pid - b.pid;
});
proArr.shift();

proArr.shift();
const cpu_mem_info = proArr.map(pro => {
return {
pid: pro.pid,
cpuPercent: pro.cpu,
totalMemory: totalMem,
usedMemory: pro.mem.private, // RSS實際內存佔用大小
memoryPercent: pro.mem.usage * 100, // 進程佔用內存百分比
virtualMemory: pro.mem.virtual, // 虛擬內存佔用大小
};
});
ctx.app.io.emit('monitor-memory-cpu', cpu_mem_info);
});
};
return exports;
};
/<code>
  • 客戶端實現 socket 監聽 monitor-memory-cpu 事件, 每 3s 中獲取到一次 Node 數據,然後繪製圖表。 圖片繪製請參考例子: https://www.igniteui.com/spline-charts/spline-area
<code>var socket = io.connect("http://localhost:7001");
socket.on("monitor-memory-cpu", function (data) {
data.forEach(info => {
info.displayCPU = info.cpuPercent;
info.displayMem = info.usedMemory;
info.displayTime = new Date().toLocaleTimeString();
cpuData.push(info);
$("#cpuChart").igDataChart("notifyInsertItem", cpuData, cpuData.length - 1, info);
$("#memoryChart").igDataChart("notifyInsertItem", cpuData, cpuData.length - 1, info);
});
});/<code>


作者:hubcarl
轉載鏈接:https://www.jianshu.com/p/cda30c741e57


分享到:


相關文章: