It is the current baseline of Hexo
(Travis CI, Ubuntu 16, Node.js 10)
| Hexo | 3.2 | 4.0 | https://github.com/hexojs/hexo/commit/c48f6a8a0a9ee212fba276c7843d83c591f28c43 |
|:---:|:---:|:---:|:---:|
| | | |
| Load Plugin/Scripts/Database (Cold) | 0.840s | 1.03s | 0.47s |
| Process Source (Cold) | 12.18s | 8.21s | 8.18s |
| Render Files (Cold) | 13.13s | 9.50s | 9.46s |
| Save Database (Cold) | 0.68s | 0.701s | 0.68s |
| Total time (Cold) | 26.83s | 19.43s | 18.80s |
| Memory Usage (Cold) | 880.67MB | 548.24MB | 555.36MB |
| | |
| Load Plugin/Scripts/Database (Hot) | 2.49s | 2.00s | 1.743s |
| Process Source (Hot) | 0.78s | 0.73s | 0.86s |
| Render Files (Hot) | 9.95s | 8.47s | 8.82s |
| Save Database (Hot) | 0.68s | 0.66s | 0.68s |
| Total time (Hot) | 13.90s | 11.85s | 11.08s |
| Memory Usage (Hot) | 692.35MB | 785.77MB | 788.05MB |
I have ran a performance test for Hexo using Travis CI with 300 posts(All posts have 1 category and 5 tags, and all markdown styles and large amounts of code blocks are included in each .md file)
All the css, js & images are deleted before hexo clean && hexo g to make sure every file generated is rendered HTML.
| | NodeJS 8 | NodeJS 10 | NodeJS 12 |
|---|:---:|:---:|:---:|
| Enable Fragment Cache & Disable Highlight.js | 26.5s | 21s | 21.5s |
| Disable Fragment Cache | 35s | 29s | 29.5s |
| Enable highlight.js only | 35s | 27s | 27.5s |
| Enable highlight.js & line_number | 38s | 30s | 30s |
| Enable highlight.js & tab_replace | 34.5s | 27s | 28s |
| Enable highlight.js, tab_replace & line_number | 38s | 30s | 31s |
Raw log can be seen here.
It seems that Disable Fragment Cache will make hexo 35% slower, Enable highlight.js will make hexo 45% slower.
And even with Fragment enabled and Highlight.js disabled, hexo can only render 33 HTML per second.
I had not heard of that setting so I wanted to help provide some context:
https://hexo.io/docs/helpers#fragment-cache
https://hexo.io/docs/templates#Optimization
https://github.com/search?q=org%3Ahexojs+fragment&type=Code
@tcrowe Fragment Cache was introduced in https://github.com/hexojs/hexo/pull/637 and the feature is depends on theme.
And Fragment Cache will be much more helpful if some time-consuming partial has been cached, see https://github.com/hexojs/hexo/issues/1769
@SukkaW Please comment out this line, rerun your tests and see how results are
https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/index.js#L14
spoiler alert cheerio and how Hexo generator spawn promises are responsible for the sluggish HTML generation and huge memory leak.
@ppoffice Yeah, I do have received a notice that meta_generator has contributed to poor performance. I will add a test with meta_generator disabled and see what we can get.
@ppoffice The result is out:

This is baseline

Set meta_generator: false in _config.yml

Comment out the filter.register('after_render:html', require('./meta_generator'));
After having a look at git blame, I found out that the meta_generator was introduced in https://github.com/hexojs/hexo/pull/3129.
And those are plugins and filters that used cheerio.
@SukkaW meta_generator config was introduced 9 days ago. You probably need to update your Hexo in your test to make the meta_generator: false work.
https://github.com/hexojs/hexo/pull/3653
_Edit_
I think the cheerio.load part takes most of the time. So the config.meta_generator should be put here https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/index.js#L14.
@curbengh @YoshinoriN
@ppoffice
https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/after_post_render/external_link.js#L8
Or like this, return before cheerio.load
https://gitlab.com/JiangTJ/performance-test-hexo/-/jobs/272854627
When there are too many tags, some of the theme code can lead to very long generation times, which I think should be avoided in hexo.
See:
https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/after_post_render/external_link.js#L8
Or like this, return beforecheerio.load
I have created a PR about this.
Anyway, I totally disagree to use cheerio traversing all the HTML generated, just for add an meta_generator tag. There should be some better ways to do that.
Anyway, I totally disagree to use cheerio traversing all the HTML generated, just for add an
meta_generatortag. There should be some better ways to do that.
open_graph approach?
This also means (with that approach) meta_generator is no longer widely adopted (across Hexo), as it depends how soon a theme can adopt it and how often users update their theme. I believe themes will adopt it eventually, albeit will take some time to reach users.
Ultimately, it depends how much we care about Wappalyzer statistics. Judging by the performance benchmark, maybe it's not worth it.
I'm very interested if commenting out the <%- open_graph() %> from theme can improve performance.
I'm preparing a PR to move meta_generator from filter to helper.
Edit: https://github.com/hexojs/hexo/pull/3669
Another (less breaking) approach by using string.replace() instead
@curbengh Currently the theme I used in performance test, hexo-theme-suka, generates Open Graph manually without using Hexo helper: https://github.com/SukkaW/hexo-theme-suka/blob/master/layout/_partial/head/open-graph.ejs
Maybe I would bring up a test with hexo-theme-landscape to test the performance of open-graph helper.
Also, cheerio is very slow... Especially when it is needed to let cheerio ran through all the hundreds of, maybe thousands of HTML files generated.
We should come up with a better idea to detect and add meta[generator] tag.
@curbengh
I just bring up the benchmark for open_graph() helper.
Raw test log can be found here: https://travis-ci.com/theme-suka/performance-test/builds/123338750
Teat A
with hexo-theme-landscape and meta_generator set to false.

Teat B
with hexo-theme-landscape and meta_generator set to false, and remove open_graph() helper using:
sed -i "s|<%- open_graph({twitter_id: theme.twitter, fb_admins: theme.fb_admins, fb_app_id: theme.fb_app_id}) %>||g" themes/landscape/layout/_partial/head.ejs

It is 28% faster without open_graph() helper.
Update
open_graph() helper detect images in the posts by default, and it is the only usage of cheerio in open_graph() helper.
I wonder if [email protected] is faster. It's currently unusable for non-ASCII chars though, but it shouldn't affect the benchmarking if we disregard how the rendered file looks like.
If you need the rendered file to be valid, you can use "cheerio": "curbengh/cheerio#decode-test" which includes the fix. you may also need to enable (Edit: decodeEntities.decodeEntities is no longer relevant.)
@curbengh
For open_graph() helper I have an idea. Since cheerio is only used to list all the images in the post at open_graph(), we could use indexOf('<img') first to determine if the current content has image or not.
If the current post has no images, then cheerio is no need to load the whole post.
Edit
During the test case, although there are 300 posts used in the benchmark, but none of them includes images, which means even without images in the content, cheerio might still slow down the speed.
Since cheerio is only used to list all the images in the post at
open_graph(), we could useindexOf('<img')first to determine if the current content has image or not.
If the current post has no images, then cheerio is no need to load the whole post.
I like the idea, though ES6 includes(substring) is preferred. Looking forward for your PR.
Edit: @SukkaW created https://github.com/hexojs/hexo/pull/3670
which means even without images in the content, cheerio might still slow down the speed.
your proposed fix is supposed to fix it, yes?
A benchmark of load cheerio when needed:
Teat A
with hexo-theme-landscape and meta_generator set to false, and cheer io is always required.

Teat B
with hexo-theme-landscape and meta_generator set to false, and only require cheerio when content includes <img.
if (!images.length && content) {
images = images.slice();
if (content.includes('<img')) {
if (!cheerio) cheerio = require('cheerio');
const $ = cheerio.load(content);
$('img').each(function() {
const src = $(this).attr('src');
if (src) images.push(src);
});
}
}

As those 300 posts includes no images, generate speed is now 17% faster. I will open a PR then.
I have updated hexo-many-posts and now 200 posts of all 300 will include images.
Then
Test A

Test B

Now it is only 4% faster.
@ppoffice Is it possible you can show me an example of this? how Hexo generator spawn promises
Also we hardly need cheerio for simple dom operations. Possibly we can just use string templates for the simple cases.
@tcrowe #3665
@tcrowe I have listed the helpers & filters that use cheerio. Maybe we can replace them with regex.
https://github.com/hexojs/hexo/issues/3663#issuecomment-521489430
I'm also interested in the performance impact of external_link filter, so I bring up a benchmark.
Test A
Baseline, external_link is set to true.

Test B
external_link is set to false.

I can see only a little improvements on generating speed, less than 0.5%.
It might because external_link filter is registered at after_post_render, which means cheerio only needs to load post content. And as meta_generator filter registered at after_render:html, cheerio needs to load all the HTML files generated (usually double to three times the number of posts due to tags and categories), that's why it caused a serious performance impact.
It might because
external_linkfilter is registered atafter_post_render, which means cheerio only needs to load post content. And asmeta_generatorfilter registered atafter_render:html, cheerio needs to load all the HTML files generated (usually double to three times the number of posts due to tags and categories), that's why it caused a serious performance impact.
I just found after_post_render only works on post, not pages. So, it is appropriate for meta_generator to be after_render:html. external_link filter should also be after_render:html as well.
I have noticed that during those benchmarks, my hexo-theme-suka is always 10% slower than hexo-theme-landscape. And I have found that hexo-theme-suka uses toc() helper and hexo-theme-landscape doesn't. So I bring up a new test with hexo-theme-suka:
Test A
Baseline, with meta_generator commented out.

Test B
Disable the toc.

Bingo! toc() helper slowed down the generation by 24.6%.
Then I looked at the toc() helper and have some interesting discovery.
Most of the theme use toc() helper like this:
toc(page.content)
which means when a theme is using toc() helper, the cheerio will load all posts' content. In the benchmarks, toc() helper will load all 300 posts' content.
I created a separate issue (https://github.com/hexojs/hexo/issues/3677) to track cheerio-related plugins. Maybe we could also create a separate issue to find out improper uses of Promise.
Replacement for highlight.js is already discussed in https://github.com/hexojs/hexo/issues/1036 and it's part of the roadmap.
This issue has been automatically marked as stale because lack of recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Most helpful comment
@curbengh
I just bring up the benchmark for
open_graph()helper.Raw test log can be found here: https://travis-ci.com/theme-suka/performance-test/builds/123338750
Teat A
with
hexo-theme-landscapeandmeta_generatorset to false.Teat B
with
hexo-theme-landscapeandmeta_generatorset to false, and removeopen_graph()helper using:It is 28% faster without
open_graph()helper.Update
open_graph()helper detect images in the posts by default, and it is the only usage of cheerio inopen_graph()helper.