Originally written by Sukka (one of the current Hexo development team member) in Simplified Chinese and translated into English by himself.
Speed is always the key to Hexo. 3 years ago, Hexo 3.2 speeds up generation speed by 2x through templates precompilation. And we are now at Hexo 4.2, with several performance improvements we successfully make the generation speed 30% faster compared to Hexo 3.2.
Benchmark
Here is how the benchmark being set up:
- Travis CI - Ubuntu Xenial 16.04
- CPU:2 Cores
- RAM:7.5 GB
- Hexo default’s theme: landscape
- 300 randomly generated posts. Each post contains all commonly used Markdown syntaxs and the code blocks for testing
highlight.js
. A unique category and three tags are also set in Front Matter of those posts.
Since Hexo 3.2, rendered contents will be cached in the warehouse
(db.json
), therefore the performance of both cold generation (hexo clean
before hexo g
) and hot generation (no hexo clean
) are tested in the benchmark. Each benchmark is performed by Cold => Hot => Cold
. Memory usage is measured using time
and the value of Resident Set Size (RSS) will be taken.
You can find the benchmark script here.
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% |
Drop cheerio dependency from Hexo
As you can see through benchmark result, there is a serious performance regression in Hexo 3.8. It turns out that the meta_generator
filter introduced in #3129 is the culprit. #3129 uses cheerio
to insert <meta name = "generator" content = "Hexo [version]">
into <head>
, thus cheerio
has to load all the HTMLs generated by Hexo into memory and parsed into DOM.
cheerio
is fast, but still there will be performance bottlenecks when traversing through hundreds of HTML files. In #3677 we made a proposal to relpace cheerio
with native API. In #3671, #3680 and #3685 we replace cheerio
wirh regex for open_graph()
helper, meta_generator
filter and external_link
filter, and in hexo-util#137 & #3850 we replace cheerio
with faster htmlparser2
. Now we have completely dropped cheerio
in Hexo 4.2
Improve Cache of Rendered HTML
mechanism
Cache of Rendered HTML
is introduced in Hexo 3.0.0-rc4 (e8e45ed
), which is an attempt to improve Hexo’s generation performance by caching rendering results. However, each route is used only once during hexo g
, so memory is consumed while no performance gained. In #3756 Cache of Rendered HTML
is disabled for hexo g
and enabled for hexo s
, so the the memory usage of hexo g
has been reduced.
Drop Lodash dependency from Hexo
Lodash is a modern JavaScript utility library that makes working with arrays, numbers, objects and strings much easier. However, as more and more new features have been brought to ES6, most of Lodash’s features could be replaced by native JavaScript.
Hexo actually started to reduce Lodash dependent a year ago, such as #3285, #3290 & warehouse#18. In #3753, we propose to gradually replace Lodash with native JavaScript by following the You don’t (may not) need Lodash/Underscore. After #3785, #3786, #3788, #3790, #3791, #3809, #3810, #3813, #3826, #3845, hexo-util#141, #3880 & #3969, we successfully dropped Lodash from Hexo. We also opened a new PR at You don’t (may not) need Lodash/Underscore to bring our _.assignIn
alternative back to the community.
Cache the return value of utility function
There are many utilities in hexo-util
, such as relative_url(from, to)
for calculating relative paths, url_for(path)
and full_url_for(path)
for trasnsforming relative paths into URLs, gravatar(mail)
for calculating gravatar URLs from E-mail address, and isExternalLink(url)
for determine whether the given URL is an external link. We found out that those functions might be called thousands of times during the Hexo generation process while the same parameters might be passed repeatedly, so the key-values of the parameters and the return value could be cached. This idea was implemented in hexo-util#162.
Future
We have added Benchmark to CI as part of the unit test in #3776. Since then, benchmark has helped us find potential performance regressions (likes #3807 & #3833) several times and avoiding the severe performance regression like #3129. And we are going to take a step further to add flamegraph to unit case in #4000, which will help us better optimizing the generation process of Hexo. For Hexo, speed is always the key.