速度就是关键! —— 我们是如何让 Hexo 4.2 的生成速度提升 30% 的

速度就是关键! —— 我们是如何让 Hexo 4.2 的生成速度提升 30% 的

技术向约 1.4 千字

本文已翻译为英文并收录于 Hexo 官网:Speed is the Key - How We Make Hexo 30% Faster

对于 Hexo 来说,速度一直都是关键。三年前,通过模板预编译,Hexo 3.2 的生成性能相比 Hexo 3.1 提升了一倍。到了 Hexo 4.2,通过一系列改进,我们成功使 Hexo 4.2 的生成速度相比 Hexo 3.2 再提升了 30%。

Benchmark

Benchmark 的设置和环境如下:

  • Travis CI - Ubuntu Xenial 16.04
    • CPU:2 Cores
    • RAM:7.5 GB
  • Hexo 默认的 landscape 主题
  • 随机产生的 300 篇文章:每篇文章都包含了所有的 Markdown 语法(标题、链接、图片),用于测试 highlight.js 的代码块;Front Matter 中设置了不重复的一个分类和三个标签。

由于 Hexo 3.2 开始将渲染结果存在 db.json 中,因此在 Benchmark 中同时测试了冷生成(heox g 之前先 hexo clean 删除 db.json)和热生成(第二次 hexo g 之前不执行 hexo clean)的性能数据。

每次 Benchmark 以 Cold => Hot 的顺序执行;内存占用使用 time 测量,取 Resident Set Size (RSS) 的值。

Benchmark 使用的脚本可以在 这里 查看。

Node.js 8

Hexo 3.2 Hexo 3.8 Hexo 4.2
Cold processing 13.585s 0% 18.572s +37% 9.210s -32%
Cold generation 13.027s 0% 50.528s +284% 8.666s -33%
Memory Usage (Cold) 815.754MB 0% 1416.309MB +69% 605.312MB -26%
Hot processing 0.668s 0% 0.712s +6% 0.732s +7%
Hot generation 11.734s 0% 46.339s +295% 7.821s -33%
Memory Usage (Hot) 702.535MB 0% 1450.719MB +106% 821.512MB +17%

Node.js 10

Hexo 3.2 Hexo 3.8 Hexo 4.2
Cold processing 11.875s 0% 15.985s +35% 8.043s -29%
Cold generation 10.308s 0% 41.339s +301% 7.450s -28%
Memory Usage (Cold) 805.633MB 0% 1440.297MB +79% 599.008MB -26%
Hot processing 0.700s 0% 0.676s -3% 0.731s +4%
Hot generation 8.322s 0% 35.453s +326% 6.420s -23%
Memory Usage (Hot) 679.082MB 0% 1447.109MB +113% 789.527MB +16%

Node.js 12

Hexo 3.2 Hexo 3.8 Hexo 4.2
Cold processing 11.454s 0% 15.626s +36% 8.381s -27%
Cold generation 10.428s 0% 37.482s +260% 7.283s -30%
Memory Usage (Cold) 1101.586MB 0% 1413.359MB +28% 580.953MB -47%
Hot processing 0.724s 0% 0.790s +9% 0.790s +9%
Hot generation 8.994s 0% 35.116s +293% 6.385s -29%
Memory Usage (Hot) 696.500MB 0% 1538.719MB +120% 600.398MB -14%

Node.js 13

Hexo 3.2 Hexo 3.8 Hexo 4.2
Cold processing 11.496s 0% 14.970s +29% 8.489s -26%
Cold generation 10.088s 0% 36.867s +265% 7.212s -28%
Memory Usage (Cold) 1104.465MB 0% 1418.273MB +28% 596.233MB -46%
Hot processing 0.724s 0% 0.776s +7% 0.756s +4%
Hot generation 7.995s 0% 33.968s +325% 6.294s -21%
Memory Usage (Hot) 761.195MB 0% 1516.078MB +99% 812.234MB +7%

从 Hexo 中去除 cheerio 依赖

正如 Benchmark 数据所示,Hexo 3.8 出现了严重的性能下降。我们发现 #3129 引入的 meta_generator 特性是罪魁祸首。#3129 使用 cheerio 往 HTML 的 <head> 中插入 <meta name="generator" content="Hexo [version]">,使得 cheerio 需要将 Hexo 生成的所有 HTML 全部存进内存并解析成 DOM。

cheerio 很快,但是在遍历上百个 HTML 文件时时还是会遇到性能瓶颈。我们在 #3677 中提出提案去除 cheerio 依赖。先后经过 #3671#3680#3685,Hexo 在 open_graph() helper、meta_generator filter 和 external_link filter 中用正则表达式替代了 cheerio,并在 hexo-util#137#3850 中将 toc() 所依赖的 cheerio 换成了更快的 htmlparser2。到了 Hexo 4.2,我们从 Hexo 中彻底去掉了 cheerio

改善 Cache of Rendered HTML 机制

Cache of Rendered HTML 最早于 Hexo 3.0.0-rc4 中 e8e45ed 引入,试图通过缓存渲染结果来改善 Hexo 的生成性能。但是在 hexo g 中每一条路径只被使用一次,所以缓存并没有起到效果、白白占用了内存。在 #3756Cache of Rendered HTML 被调整为只在 hexo s 下启用,大幅减少了 hexo g 的内存占用。

从 Hexo 中去除 Lodash 依赖

Lodash 是个实用的工具库,大大降低了 Array、Number、Objects、String 的使用难度。不过随着 ES6 的新特性不断增加,Lodash 的大部分功能都有了 Native 替代。

Hexo 其实很早就开始减少对 Lodash 的依赖了,如 #3285#3290warehouse#18。在 #3753 中,我们提出可以参考 You don't (may not) need Lodash/Underscore 逐步将 Lodash 替换为 Native JavaScript。经过 #3785#3786#3788#3790#3791#3809#3810#3813#3826#3845hexo-util#141#3880#3969 这一系列 PR,我们从 Hexo 中去除了 Lodash。我们也在 You-Dont-Need-Lodash-Underscore 提交了 PR、将我们的 _.assignIn 的 Native 替代方案带给社区。

缓存实用函数的返回值

Hexo 的 hexo-util 中有许多的实用函数,如计算相对路径的 relative_url(from, to)、补全相对路径为 URL 的 url_for(path)full_url_for(path)、从 EMail 计算 gravatar URL 的 gravatar(mail)、判断一个链接是否为外部链接的 isExternalLink(url)。在 测试 中我们发现在 Hexo 生成过程中这些函数可能会被调用上千次,但在大部分调用中传入的参数都是相同的,因此可以把传入的参数和函数的返回值的键值对缓存起来。这一构想在 hexo-util#162 中被实现。

未来

#3776 中我们将 Benchmark 作为单元测试的一部分添加到 CI。之后 Benchmark 多次帮助我们找到潜在的性能问题(#3807#3833),避免了 #3129 那样造成严重的性能下降的情况再次发生。而在 #4000 中我们计划进一步将生成火焰图也作为单元测试的一部分,帮助我们继续优化 Hexo 的生成过程。对于 Hexo 来说,速度一直都是关键。

速度就是关键! —— 我们是如何让 Hexo 4.2 的生成速度提升 30% 的
本文作者
Sukka
发布于
2019-12-23
许可协议
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!
如果你喜欢我的文章,或者我的文章有帮到你,可以考虑一下打赏作者
评论加载中...