速度是关键 - 我们如何使 Hexo 快 30%

原文由 Sukka(现任 Hexo 开发团队成员之一)以 简体中文 撰写,并由他本人翻译成英文。

速度一直是 Hexo 的关键。3 年前,Hexo 3.2 通过模板预编译将生成速度提高了 2 倍。现在我们已经到了 Hexo 4.2,通过多项性能改进,我们成功地将生成速度提高了 30%,与 Hexo 3.2 相比。

基准测试

以下是基准测试的设置方式

  • Travis CI - Ubuntu Xenial 16.04
    • CPU:2 核
    • RAM:7.5 GB
  • Hexo 默认主题:landscape
  • 300 个随机生成的帖子。每个帖子都包含所有常用的 Markdown 语法和用于测试 highlight.js 的代码块。还为这些帖子的前端物质设置了唯一的类别和三个标签。

自 Hexo 3.2 以来,渲染后的内容将被缓存到 warehousedb.json)中,因此在基准测试中测试了冷生成(hexo clean 之前 hexo g)和热生成(没有 hexo clean)的性能。每个基准测试都是通过 Cold => Hot => Cold 执行的。内存使用量是使用 time 测量的,并将采用驻留集大小 (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.754 MB 0% 1416.309 MB +69% 605.312 MB -26%
热处理 0.668 秒 0% 0.712 秒 +6% 0.732 秒 +7%
热生成 11.734 秒 0% 46.339 秒 +295% 7.821 秒 -33%
内存使用量 (热) 702.535 MB 0% 1450.719 MB +106% 821.512 MB +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.633 MB 0% 1440.297 MB +79% 599.008 MB -26%
热处理 0.700 秒 0% 0.676 秒 -3% 0.731 秒 +4%
热生成 8.322 秒 0% 35.453 秒 +326% 6.420 秒 -23%
内存使用量 (热) 679.082 MB 0% 1447.109 MB +113% 789.527 MB +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.586 MB 0% 1413.359 MB +28% 580.953 MB -47%
热处理 0.724 秒 0% 0.790 秒 +9% 0.790 秒 +9%
热生成 8.994 秒 0% 35.116 秒 +293% 6.385 秒 -29%
内存使用量 (热) 696.500 MB 0% 1538.719 MB +120% 600.398 MB -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.465 MB 0% 1418.273 MB +28% 596.233 MB -46%
热处理 0.724 秒 0% 0.776 秒 +7% 0.756 秒 +4%
热生成 7.995 秒 0% 33.968 秒 +325% 6.294 秒 -21%
内存使用量 (热) 761.195 MB 0% 1516.078 MB +99% 812.234 MB +7%

从 Hexo 中删除 cheerio 依赖

正如您在基准测试结果中看到的那样,Hexo 3.8 中存在严重的性能倒退。事实证明,在 #3129 中引入的 meta_generator 过滤器是罪魁祸首。#3129 使用 cheerio<meta name = "generator" content = "Hexo [version]"> 插入到 <head> 中,因此 cheerio 必须将 Hexo 生成的所有 HTML 加载到内存中并解析为 DOM。

cheerio 很快,但遍历数百个 HTML 文件时仍然会出现性能瓶颈。在 #3677 中,我们提出了用原生 API 替换 cheerio 的建议。在 #3671#3680#3685 中,我们将 cheerio 用正则表达式替换为 open_graph() 帮助程序、meta_generator 过滤器和 external_link 过滤器,在 hexo-util#137#3850 中,我们将 cheerio 用更快的 htmlparser2 替换。现在,我们在 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#3290warehouse#18。在 #3753 中,我们建议通过遵循 您(可能)不需要 Lodash/Underscore 来逐步用原生 JavaScript 替换 Lodash。在 #3785#3786#3788#3790#3791#3809#3810#3813#3826#3845hexo-util#141#3880#3969 之后,我们成功地从 Hexo 中删除了 Lodash。我们还在 您(可能)不需要 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 中将基准测试添加到了 CI 中,作为单元测试的一部分。从那时起,基准测试帮助我们多次发现潜在的性能倒退(例如 #3807#3833)并避免了像 #3129 这样的严重性能倒退。我们将在 #4000 中更进一步,将火焰图添加到单元测试用例中,这将帮助我们更好地优化 Hexo 的生成过程。对于 Hexo 来说,速度一直是关键。