Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memoize manual section presenter #3516

Merged
merged 3 commits into from
Jan 15, 2025
Merged

Memoize manual section presenter #3516

merged 3 commits into from
Jan 15, 2025

Conversation

richardTowers
Copy link
Contributor

We're currently calling the main method, and then looping over the result. In each loop, we call the method again so that we can get the length. See manual_section.html.erb, which does roughly:

@content_item.main.each.with_index do |item, index|
 # ...
 {
   data_attributes: {
     ga4_event: {
       # ...
       index: index,
       index_total: @content_item.main.length,

... the effect of this is that we run the main method n times, where n is the number of <h2> elements in the body. This results in us spending a lot of time parsing the same document and running the same CSS / XPath selectors on it, which adds up to a lot of time.

Memoizing the result of the main method shaves several seconds off the time to render www.gov.uk/guidance/complete-the-school-census/data-items-2024-to-2025 on my machine.

The flamegraph before these changes (using the wonderful rack-mini-profiler) shows manual_section.html.erb taking over 2 seconds to render:

flamegraph before changes

Note that ManualSectionPresenter#main is called many times.

After the changes in this PR, this reduces to 186ms:

flamegraph after changes

These flamegraphs also identified a further ~70ms we could save by memoizing the intro method, so I've done that too.

I did look at some other optimizations (e.g. traversing the nokogiri document manually, instead of using css / xpaths), but once it's memoized those only save microseconds so it's not worth it.

⚠️ This repo is Continuously Deployed: make sure you follow the guidance ⚠️

We're currently calling the main method, and then looping over the
result. In each loop, we call the method again so that we can get the
length. See manual_section.html.erb, which does roughly:

   @content_item.main.each.with_index do |item, index|
     # ...
     {
       data_attributes: {
         ga4_event: {
           # ...
           index: index,
           index_total: @content_item.main.length,

... the effect of this is that we run the main method n times, where n
is the number of <h2> elements in the body. This results in us spending
a lot of time parsing the same document and running the same CSS / XPath
selectors on it, which adds up to a lot of time.

Memoizing the result of the main method shaves several seconds off the
time to render www.gov.uk/guidance/complete-the-school-census/data-items-2024-to-2025
on my machine.

I did look at some other optimizations (e.g. traversing the nokogiri
document manually, instead of using css / xpaths), but once it's
memoized those only save microseconds so it's not worth it.
This saves about 70ms re-parsing the body, as the intro method is called
twice.
@govuk-ci govuk-ci temporarily deployed to government-frontend-pr-3516 January 15, 2025 09:45 Inactive
@govuk-ci govuk-ci temporarily deployed to government-frontend-pr-3516 January 15, 2025 09:46 Inactive
@govuk-ci govuk-ci temporarily deployed to government-frontend-pr-3516 January 15, 2025 10:02 Inactive
returning early from a begin block won't assign the variable, but it
sort of looks like it will. Clearer / more explicit to have the early
returns outside the block.
@govuk-ci govuk-ci temporarily deployed to government-frontend-pr-3516 January 15, 2025 10:03 Inactive
Copy link
Contributor

@hannako hannako left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you do that rack-mini-profiler flame diagram thing? I smell a tech fortnightly presentation!!

Maybe we could also help by adding

length = @content_item.main.length

here in the template. Also maybe some of that template logic should be in the presenter? One for another time!

@richardTowers
Copy link
Contributor Author

Definitely a tech fortnightly talk coming yeah. It's pretty easy though actually:

  • Install the rack-mini-profiler and stackprof gems
  • Disable garbage collection for the controler action in question
  • Visit the page and append ?pp=flamegraph to the URL

9d87d6a

Previously I've used rbspy for these things, which is way more faff to set up.

@richardTowers richardTowers merged commit 2eeac75 into main Jan 15, 2025
11 checks passed
@richardTowers richardTowers deleted the faster-manuals branch January 15, 2025 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants