Performance Expectations

Hi,

I was wondering what the performance expectations for building with Hugo were. On Google I’ve seen it discussed, but nothing really pointed to what was realistic expectations.

When I build with no metric flags, I get the following:

0 of 28 drafts rendered
0 of 5 futures rendered
0 expired content
407 regular pages created
2496 other pages created
0 non-page files copied
1455 paginator pages created
52 categories created
1191 tags created
total in 4996 ms

When I build with stepAnalysis enabled:

Go initialization:
	801.569904ms (802.261564ms)	    8.90 MB 	101326 Allocs
initialize:
	73.453µs (802.367181ms)	    0.01 MB 	173 Allocs
load data:
	1.503525ms (803.929172ms)	    0.14 MB 	2387 Allocs
load i18n:
	133ns (803.957055ms)	    0.00 MB 	0 Allocs
read pages from source:
	52.48363ms (856.547989ms)	   23.38 MB 	163964 Allocs
convert source:
	31.045602ms (887.773711ms)	   52.75 MB 	210810 Allocs
build Site meta:
	4.06214ms (891.897982ms)	    0.89 MB 	25172 Allocs
prepare pages:
	73.702331ms (965.685529ms)	   26.19 MB 	96679 Allocs
render and write aliases:
	46.122µs (965.766131ms)	    0.00 MB 	7 Allocs
render and write pages:
	4.203848535s (5.169665738s)	 1015.35 MB 	29377419 Allocs
render and write Sitemap:
	41.759725ms (5.211484544s)	    5.90 MB 	116640 Allocs
render and write robots.txt:
	10.317µs (5.211545297s)	    0.00 MB 	7 Allocs
render and write 404:
	11.535675ms (5.223144317s)	    1.05 MB 	29569 Allocs
render and write pages:
	575.293142ms (5.79856103s)	  103.74 MB 	1290925 Allocs

Finally, when I build with templateMetrics enabled:

Template Metrics:

     cumulative       average       maximum         
       duration      duration      duration  count  template
     ----------      --------      --------  -----  --------
   9.155789234s    4.844332ms   36.233396ms   1890  theme/partials/widgets/sidebar/recent.html
    8.68188007s    5.866135ms   16.486078ms   1480  theme/_default/list.html
   2.934280275s    7.209533ms   38.949899ms    407  theme/_default/single.html
   678.216743ms     543.442µs   56.470446ms   1248  theme/_default/rss.xml
   560.940901ms     296.637µs   12.752235ms   1891  theme/partials/head.html
   246.858965ms     606.533µs    3.369278ms    407  theme/_default/single.content.html
   148.629501ms      78.639µs    5.318497ms   1890  theme/partials/widgets/sidebar/adsense2.html
   140.074754ms      74.113µs      835.09µs   1890  theme/partials/widgets/sidebar/social.html
   123.329631ms      65.253µs    1.599514ms   1890  theme/partials/widgets/sidebar/adsense1.html
   112.173083ms       59.35µs     963.263µs   1890  theme/partials/widgets/sidebar/donate.html
   111.962994ms      59.239µs    1.019162ms   1890  theme/partials/navigation/navigation-fixed.html
   107.786412ms      57.029µs    4.250513ms   1890  theme/partials/widgets/sidebar/subscribe.html
    95.226925ms      50.357µs    1.250092ms   1891  theme/partials/footer.html
    76.275361ms      40.335µs     896.228µs   1891  theme/partials/foot.html
    55.756381ms     136.993µs      552.89µs    407  theme/partials/widgets/content/tags.html
     53.73365ms     133.665µs    1.536781ms    402  theme/partials/widgets/content/share.html
    50.128526ms   25.064263ms   42.581587ms      2  theme/_default/terms.html
    38.706963ms   38.706963ms   38.706963ms      1  _internal/_default/sitemap.xml
    36.409754ms      24.601µs     400.251µs   1480  theme/partials/pagination.html
    28.189216ms      14.914µs       889.3µs   1890  theme/partials/widgets/sidebar/search.html
     22.72955ms      55.846µs     1.35901ms    407  theme/partials/widgets/content/author.html
     9.365041ms      23.009µs     716.354µs    407  theme/partials/disqus.html
     8.112286ms    8.112286ms    8.112286ms      1  theme/404.html
     3.272399ms      25.565µs     140.569µs    128  _internal/shortcodes/youtube.html
     2.480729ms       1.312µs     198.671µs   1890  theme/partials/widgets/sidebar/youtube.html
     1.050769ms    1.050769ms    1.050769ms      1  theme/index.html
      401.648µs      26.776µs      61.383µs     15  theme/shortcodes/audio.html
          229µs         229µs         229µs      1  theme/partials/navigation/navigation.html
      189.612µs     189.612µs     189.612µs      1  theme/shortcodes/slideshare.html
       53.603µs      53.603µs      53.603µs      1  theme/shortcodes/codepen.html

That is a lot of information, most of which I am not sure how to decipher. If I had to guess, I’d say I have two problems:

  1. I have a lot of taxonomy terms which is resulting in a lot of pages being built.
  2. I have a partial for displaying my five most recent posts on the sidebar of each page.

Are those the reasons why my build time is nearly 5 seconds? Based on my number of pages and taxonomy information, what should my build time be? Are there any tips and tricks that can be used to optimize my Hugo site or theme to improve the build time?

Any information on how to properly troubleshoot the metrics and apply changes would be helpful. My site is found here:

https://www.thepolyglotdeveloper.com/blog

That should give you an idea on what I’m trying to accomplish in case you need a visualization.

Thanks,

Based upon the step analysis, it looks like most of your build time (4.2 seconds) is spend rendering pages, which appears to be a result of the number of categories and tags you have.

If I were you, I’d first look at the top entries from the Template Metrics output. The cumulative duration column lists the CPU time spend executing a given template. Things to consider:

  • Use cached partials where possible. For example, you probably only need to execute theme/partials/widgets/sidebar/donate.html once.

  • See if you can optimize some “related content” areas with Hugo’s new Related Content features. It was added in v0.27.

You can always reduce the number of tags and taxonomies, but I’m assuming you’re using them wisely. :slight_smile:

1 Like

Interesting on the cached partials! I do have a question on it though. How do I know whether I need something to be re-rendered on every invocation? It isn’t obvious to me what is an acceptable scenario for partial vs partialCached.

Can you clear the above up because it looks promising!

I don’t believe related content applies to me. I’m showing recent content, but not related. My recent content widget on every page simply uses the following:

{{ range first 5 (where .Site.Pages "Type" "post") }}
                        <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
                    {{ end }}

I am working on reducing my taxonomy terms. Back when I was on WordPress I got out of control. I’m thinking I can shave off about 25% of them.

You can use partialCached, I believe, on any partial that will have the same result on every template it’s on.

I’m probably not a Hugo power user, but do you have an example where a partial would not be the same on every page?

A cached partial is only executed/generated once. If you have a partial that generates the same output 1800 times, you should cache that puppy.

You can also use cached variants. So, if a menu renders the same output for all pages under the same section, you could do something like:

{{ partialCached "menu.html" . .Section }}

That causes the menu.html partial to only be rendered once for each value of .Section.

A partial that included something unique about the page calling it shouldn’t be cached.

Your “recent content” partial is a great example of a partial that should be cached. It should generate the same results on every page, so you really only need to render it once.

Awesome!

I swapped out my partial with partialCached and it dropped the build time from 5000ms to 500ms. I feel like this should be more heavily promoted because those are huge changes in performance.

Thanks for the performance tips!

2 Likes

Funny. I started working on a Build Performance page yesterday. :slight_smile:

Was the above explanation about cached partials helpful? I’ll try to work in a clearer explanation.

Yes, I think it was very helpful!

I think I need to play around with it some more so I don’t mess something up, but for the most part the performance improvements are great.

Thanks for the help!

Side question though.

Say my partial has something like this:

{{ if eq .Type "post" }}

Will the conditional logic still apply to every page, whether it be post or page? For example, within my Disqus partial I have it first check to see if it is a page or a post. If it is a post then apply Disqus. I include the partial on every template regardless of what will show.

In this scenario, will it cache and then always be one or the other regardless of the type?

Does that make sense?

You don’t want to cache that partial outright because the output varies based upon .Type. If that template really outputs the same thing based only upon .Type, I’d use a cached variant:

{{ partialCached "disqus.html" . .Type }}

That will execute the template once for each value of .Type.

What happens in the background is that we use TemplateName+Variants as the cache key.

Interesting!

I need to play around with this some more. Definitely include everything we discussed in your documentation. I’m sure people are smarter than me at this, but I’m sure it will help.

2 Likes

The promotional tag line of Hugo is “1 ms per rendered page” (it is on the front page of gohugo.io). Your site produces around 5000 pages built in 5 seconds, which sounds about in line with the promotion – it is also very, very fast compared to most other static site generators on the planet.

There are plenty ways to create slow Hugo sites, though. The new template metrics feature is gold to spot these – often cachedPartial will be a fix.