速度一直是 Hexo 的關鍵。3 年前,Hexo 3.2 通過模板預編譯將生成速度加快了 2 倍。而現在我們已來到 Hexo 4.2,通過幾項性能改進,我們成功地使生成速度比 Hexo 3.2 快了 30%。
效能基準
以下是基準的設定方式
- Travis CI - Ubuntu Xenial 16.04
- CPU:2 核心
- RAM:7.5 GB
- Hexo 預設主題:landscape
- 300 篇隨機生成的文章。每篇文章都包含所有常用的 Markdown 語法和用於測試
highlight.js
的程式碼區塊。這些文章的前言 (Front Matter) 中也設定了唯一的分類和三個標籤。
自 Hexo 3.2 起,渲染的內容將被快取到 warehouse
(db.json
) 中,因此在基準測試中會測試冷啟動生成 (hexo clean
後執行 hexo g
) 和熱啟動生成 (沒有 hexo clean
) 的性能。每次基準測試都會以 冷啟動 => 熱啟動 => 冷啟動
的順序執行。記憶體使用量會使用 time
來測量,並取常駐記憶體集 (Resident Set Size, RSS) 的值。
您可以在這裡找到基準測試腳本。
Node.js 8
Hexo 3.2 | Hexo 3.8 | Hexo 4.2 | ||||
---|---|---|---|---|---|---|
冷處理 | 13.585 秒 | 0% | 18.572 秒 | +37% | 9.210 秒 | -32% |
冷啟動生成 | 13.027 秒 | 0% | 50.528 秒 | +284% | 8.666 秒 | -33% |
記憶體使用量 (冷啟動) | 815.754MB | 0% | 1416.309MB | +69% | 605.312MB | -26% |
熱處理 | 0.668 秒 | 0% | 0.712 秒 | +6% | 0.732 秒 | +7% |
熱啟動生成 | 11.734 秒 | 0% | 46.339 秒 | +295% | 7.821 秒 | -33% |
記憶體使用量 (熱啟動) | 702.535MB | 0% | 1450.719MB | +106% | 821.512MB | +17% |
Node.js 10
Hexo 3.2 | Hexo 3.8 | Hexo 4.2 | ||||
---|---|---|---|---|---|---|
冷處理 | 11.875 秒 | 0% | 15.985 秒 | +35% | 8.043 秒 | -29% |
冷啟動生成 | 10.308 秒 | 0% | 41.339 秒 | +301% | 7.450 秒 | -28% |
記憶體使用量 (冷啟動) | 805.633MB | 0% | 1440.297MB | +79% | 599.008MB | -26% |
熱處理 | 0.700 秒 | 0% | 0.676 秒 | -3% | 0.731 秒 | +4% |
熱啟動生成 | 8.322 秒 | 0% | 35.453 秒 | +326% | 6.420 秒 | -23% |
記憶體使用量 (熱啟動) | 679.082MB | 0% | 1447.109MB | +113% | 789.527MB | +16% |
Node.js 12
Hexo 3.2 | Hexo 3.8 | Hexo 4.2 | ||||
---|---|---|---|---|---|---|
冷處理 | 11.454 秒 | 0% | 15.626 秒 | +36% | 8.381 秒 | -27% |
冷啟動生成 | 10.428 秒 | 0% | 37.482 秒 | +260% | 7.283 秒 | -30% |
記憶體使用量 (冷啟動) | 1101.586MB | 0% | 1413.359MB | +28% | 580.953MB | -47% |
熱處理 | 0.724 秒 | 0% | 0.790 秒 | +9% | 0.790 秒 | +9% |
熱啟動生成 | 8.994 秒 | 0% | 35.116 秒 | +293% | 6.385 秒 | -29% |
記憶體使用量 (熱啟動) | 696.500MB | 0% | 1538.719MB | +120% | 600.398MB | -14% |
Node.js 13
Hexo 3.2 | Hexo 3.8 | Hexo 4.2 | ||||
---|---|---|---|---|---|---|
冷處理 | 11.496 秒 | 0% | 14.970 秒 | +29% | 8.489 秒 | -26% |
冷啟動生成 | 10.088 秒 | 0% | 36.867 秒 | +265% | 7.212 秒 | -28% |
記憶體使用量 (冷啟動) | 1104.465MB | 0% | 1418.273MB | +28% | 596.233MB | -46% |
熱處理 | 0.724 秒 | 0% | 0.776 秒 | +7% | 0.756 秒 | +4% |
熱啟動生成 | 7.995 秒 | 0% | 33.968 秒 | +325% | 6.294 秒 | -21% |
記憶體使用量 (熱啟動) | 761.195MB | 0% | 1516.078MB | +99% | 812.234MB | +7% |
移除 Hexo 的 cheerio 相依性
如您從基準測試結果所見,Hexo 3.8 中存在嚴重的效能倒退。結果發現,在 #3129 中引入的 meta_generator
過濾器是罪魁禍首。#3129 使用 cheerio
將 <meta name = "generator" content = "Hexo [版本]">
插入到 <head>
中,因此 cheerio
必須將 Hexo 生成的所有 HTML 載入到記憶體中並解析成 DOM。
cheerio
速度很快,但當遍歷數百個 HTML 檔案時仍然會出現效能瓶頸。在 #3677 中,我們提出使用原生 API 來取代 cheerio
。在 #3671、#3680 和 #3685 中,我們使用 regex 取代 cheerio
用於 open_graph()
輔助函數、meta_generator
過濾器和 external_link
過濾器,並在 hexo-util#137 & #3850 中,我們使用更快的 htmlparser2
取代 cheerio
。現在我們已在 Hexo 4.2 中完全移除 cheerio
。
改進「渲染的 HTML 快取」機制
「渲染的 HTML 快取」機制是在 Hexo 3.0.0-rc4 中引入的 (e8e45ed
),這是嘗試通過快取渲染結果來提高 Hexo 的生成效能。然而,在執行 hexo g
時,每個路由只會使用一次,因此會消耗記憶體,但卻沒有獲得效能提升。在 #3756 中,「渲染的 HTML 快取」機制被停用用於 hexo g
,並啟用用於 hexo s
,因此 hexo g
的記憶體使用量已降低。
移除 Hexo 的 Lodash 相依性
Lodash 是一個現代 JavaScript 工具庫,它使處理陣列、數字、物件和字串更加容易。然而,隨著越來越多新功能被引入到 ES6 中,Lodash 的大部分功能都可以被原生 JavaScript 取代。
Hexo 實際上在一年前就開始減少對 Lodash 的依賴,例如 #3285、#3290 & warehouse#18。在 #3753 中,我們建議通過遵循 You don’t (may not) need Lodash/Underscore 來逐步使用原生 JavaScript 取代 Lodash。經過 #3785、#3786、#3788、#3790、#3791、#3809、#3810、#3813、#3826、#3845、hexo-util#141、#3880 & #3969 之後,我們成功地從 Hexo 中移除了 Lodash。我們還在 You don’t (may not) need Lodash/Underscore 開啟了一個新的 PR,將我們的 _.assignIn
替代方案帶回社群。
快取工具函數的返回值
hexo-util
中有許多工具函數,例如:用於計算相對路徑的 relative_url(from, to)
、用於將相對路徑轉換為 URL 的 url_for(path)
和 full_url_for(path)
、用於從電子郵件地址計算 Gravatar URL 的 gravatar(mail)
,以及用於判斷給定的 URL 是否為外部連結的 isExternalLink(url)
。我們發現這些函數在 Hexo 生成過程中可能會被調用數千次,同時可能會重複傳遞相同的參數,因此可以快取參數和返回值的鍵值對。這個想法在 hexo-util#162 中實現。
未來
我們已在 #3776 中,將效能基準測試 (Benchmark) 作為單元測試的一部分加入 CI。自此之後,效能基準測試已多次協助我們找出潛在的效能回歸問題(例如 #3807 & #3833),並避免了如 #3129 這類嚴重的效能回歸。我們將進一步在 #4000 中,將火焰圖 (flamegraph) 加入單元測試案例,這將有助於我們更好地最佳化 Hexo 的產生過程。對於 Hexo 來說,速度始終是關鍵。