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
480
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
短期間での新規プロダクト開発における「コスパの良い」Goのテスト戦略」 / kamakura.go
n3xem
2
170
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
140
42 best practices for Symfony, a decade later
tucksaun
1
180
As an Engineers, let's build the CRM system via LINE Official Account 2.0
clonn
1
670
「Chatwork」Android版アプリを 支える単体テストの現在
okuzawats
0
180
tidymodelsによるtidyな生存時間解析 / Japan.R2024
dropout009
1
760
ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12
yue4u
2
460
これでLambdaが不要に?!Step FunctionsのJSONata対応について
iwatatomoya
2
3.6k
生成AIでGitHubソースコード取得して仕様書を作成
shukob
0
280
なまけものオバケたち -PHP 8.4 に入った新機能の紹介-
tanakahisateru
1
120
Monixと常駐プログラムの勘どころ / Scalaわいわい勉強会 #4
stoneream
0
270
RWC 2024 DICOM & ISO/IEC 2022
m_seki
0
210
Featured
See All Featured
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.2k
How To Stay Up To Date on Web Technology
chriscoyier
789
250k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
1.2k
Become a Pro
speakerdeck
PRO
26
5k
Facilitating Awesome Meetings
lara
50
6.1k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Automating Front-end Workflow
addyosmani
1366
200k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
BBQ
matthewcrist
85
9.4k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
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?