Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Fixing a performance issue with the mustache gem
Search
Dominic Baggott
October 12, 2015
Programming
0
500
Fixing a performance issue with the mustache gem
@alicebartlett and I gave this talk at LRUG, on Monday 12th October 2015.
Dominic Baggott
October 12, 2015
Tweet
Share
Other Decks in Programming
See All in Programming
Cursor AI Agentと伴走する アプリケーションの高速リプレイス
daisuketakeda
1
110
Development of an App for Intuitive AI Learning - Blockly Summit 2025
teba_eleven
0
110
FormFlow - Build Stunning Multistep Forms
yceruto
1
170
エラーって何種類あるの?
kajitack
5
140
GoのWebAssembly活用パターン紹介
syumai
3
10k
Datadog RUM 本番導入までの道
shinter61
1
270
プロダクト開発でも使おう 関数のオーバーロード
yoiwamoto
0
150
Spring gRPC で始める gRPC 入門 / Introduction to gRPC with Spring gRPC
mackey0225
2
490
從零到一:搭建你的第一個 Observability 平台
blueswen
1
910
イベントストーミングから始めるドメイン駆動設計
jgeem
4
830
TypeScript LSP の今までとこれから
quramy
1
500
生成AIコーディングとの向き合い方、AIと共創するという考え方 / How to deal with generative AI coding and the concept of co-creating with AI
seike460
PRO
1
250
Featured
See All Featured
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
3.9k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
52
2.8k
Visualization
eitanlees
146
16k
Rails Girls Zürich Keynote
gr2m
94
14k
Build The Right Thing And Hit Your Dates
maggiecrowley
36
2.7k
Gamification - CAS2011
davidbonilla
81
5.3k
GitHub's CSS Performance
jonrohan
1031
460k
Unsuck your backbone
ammeep
671
58k
Rebuilding a faster, lazier Slack
samanthasiow
81
9k
Building Applications with DynamoDB
mza
95
6.4k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
228
22k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Transcript
fixing a performance issue with the mustache gem (a bit
less boring than it sounds)
@alicebartlett & @evilstreak
We used to work at GDS, on gov.uk
In this talk: - Adding a feature - Finding a
bug - working around the bug - fixing the bug
GOV.uk is the best place to find government services and
information
These are Manuals They are multi- page long form documents
New feature: search within a manual
This is search within a manual A list of results
from a Manual with a nested list of results from the rest of GOV.UK
From this:
To this: From this:
None
Search pages use mustache (specifically: the ruby implementation of mustache
via the mustache gem)
From this:
To this: From this:
To this: then from this:
This is very uninteresting.
Using partials in this way is in the docs: from:
https://mustache.github.io/mustache.5.html#Partials
full PR is here: https://github.com/alphagov/frontend/pull/784
Time to deploy!
This should have been straightforward
None
-
-
We deploy to staging
We proceed to production
Slowly, shit hits the fan.
-
We rollback and investigate on staging
Breaking things isn’t bad if you have a good plan
to recover
Profiling
We found out the problem was this CPU spike
# Profile the code RubyProf.start ... [code to profile] ...
result = RubyProf.stop # Print a flat profile to text printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) Measuring code with ruby-prof is easy
Profile of our code %self total self wait child calls
name 5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags 3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval 3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app 2.98 0.047 0.047 0.000 0.000 6 <Class::Dir>#[] 2.52 0.042 0.040 0.001 0.001 74 <Module::ActiveSupport::Multibyte> 2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp 2.08 0.051 0.033 0.000 0.018 46 <Module::Marshal>#load 1.99 0.032 0.032 0.000 0.000 6 Kernel#require 1.98 0.032 0.032 0.000 0.000 2181 String#to_s 1.67 0.506 0.027 0.000 0.480 1007 *Array#each 1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize 1.48 0.024 0.024 0.000 0.000 168 <Class::File>#exist? 1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position 1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text
Profile of our code %self total self wait child calls
name 5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags 3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval 3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app 2.98 0.047 0.047 0.000 0.000 6 <Class::Dir>#[] 2.52 0.042 0.040 0.001 0.001 74 <Module::ActiveSupport::Multibyte> 2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp 2.08 0.051 0.033 0.000 0.018 46 <Module::Marshal>#load 1.99 0.032 0.032 0.000 0.000 6 Kernel#require 1.98 0.032 0.032 0.000 0.000 2181 String#to_s 1.67 0.506 0.027 0.000 0.480 1007 *Array#each 1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize 1.48 0.024 0.024 0.000 0.000 168 <Class::File>#exist? 1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position 1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text Method name Time Calls
Profile of our code %self total self wait child calls
name 5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags 3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval 3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app 2.98 0.047 0.047 0.000 0.000 6 <Class::Dir>#[] 2.52 0.042 0.040 0.001 0.001 74 <Module::ActiveSupport::Multibyte> 2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp 2.08 0.051 0.033 0.000 0.018 46 <Module::Marshal>#load 1.99 0.032 0.032 0.000 0.000 6 Kernel#require 1.98 0.032 0.032 0.000 0.000 2181 String#to_s 1.67 0.506 0.027 0.000 0.480 1007 *Array#each 1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize 1.48 0.024 0.024 0.000 0.000 168 <Class::File>#exist? 1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position 1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text
Before and after self calls name self calls name 0.051
2 BasicObject#instance_eva 0.086 675 Mustache::Parser#scan_ta 0.042 6 <Class::Dir>#[] 0.062 36 BasicObject#instance_eva 0.014 110 Mustache::Parser#scan_ta 0.053 1 ActionView::CompiledTemp 0.014 337 *Array#each 0.047 6 <Class::Dir>#[] 0.004 145 Sprockets::Asset#depende 0.040 74 <Module::ActiveSupport:: 0.004 149 *Array#map 0.037 1095 Mustache::Parser#regexp 0.003 110 Mustache::Parser#scan_te 0.033 46 <Module::Marshal>#load 0.003 109 Mustache::Parser#positio 0.032 6 Kernel#require 0.003 110 Mustache::Parser#scan_un 0.032 2181 String#to_s 0.002 2 <Class::IO>#binread 0.027 1007 *Array#each 0.002 186 <Class::File>#exist? 0.025 972 Psych::ScalarScanner#tok 0.002 774 Kernel#is_a? 0.024 168 <Class::File>#exist? 0.002 186 Hike::Trail#stat 0.021 663 Mustache::Parser#positio 0.002 232 String#split 0.021 675 Mustache::Parser#scan_te
compiling templates is slow and partials are compiled before each
use
Recursion
Our results page has a list within a list
This is broken This is OK
- Iteration - Repetition - Recursion
- Iteration - Repetition - Recursion
Repetition
Repetition
This is fast but ghastly
- Iteration - Repetition ?? - Recursion
Recursion is a bit of a dick move
Recursion: Use responsibly
_results.mustache: {{#results}} {{#is_multiple_results}} <ol class= >{{>_results}}</ol> {{/is_multiple_results}} {{^is_multiple_results}} <li> result
code goes here </li> {{/is_multiple_results}} {{/results}}
_results.mustache: {{#results}} {{#is_multiple_results}} <ol class= >{{>_results}}</ol> /*Call self*/ {{/is_multiple_results}} {{^is_multiple_results}}
<li> result code goes here </li> {{/is_multiple_results}} {{/results}}
The problem with this is it’s non-obvious
- Iteration - Repetition ?? - Recursion ??
None
Reader, we chose the recursion.
Fixing the gem
Fixing the gem was the right thing to do
Fixing the gem prevents trouble in the future
Looking for prior art
The fix
We added a benchmark instead of a test
Writing the pull request https://github.com/mustache/mustache/pull/205
Explain the problem
Explain the change
Be proactive about objections
Keep nudging for progress
Over half our effort went on getting the change accepted
OK thanks bye
Links
Questions?