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
510
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
目的で駆動する、AI時代のアーキテクチャ設計 / purpose-driven-architecture
minodriven
11
3.6k
関数の挙動書き換える
takatofukui
4
750
モビリティSaaSにおけるデータ利活用の発展
nealle
1
650
All(?) About Point Sets
hole
0
220
データファイルをAWSのDWHサービスに格納する / 20251115jawsug-tochigi
kasacchiful
2
100
しっかり学ぶ java.lang.*
nagise
1
460
Flutterチームから作る組織の越境文化
findy_eventslides
0
620
GeistFabrik and AI-augmented software development
adewale
PRO
0
200
Developing Specifications - Jakarta EE: a Real World Example
ivargrimstad
0
250
Honoを技術選定したAI要件定義プラットフォームAcsimでの意思決定
codenote
0
270
[堅牢.py #1] テストを書かない研究者に送る、最初にテストを書く実験コード入門 / Let's start your ML project by writing tests
shunk031
11
6.1k
JEP 496 と JEP 497 から学ぶ耐量子計算機暗号入門 / Learning Post-Quantum Crypto Basics from JEP 496 & 497
mackey0225
2
500
Featured
See All Featured
Stop Working from a Prison Cell
hatefulcrawdad
273
21k
Designing for humans not robots
tammielis
254
26k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.1k
Fireside Chat
paigeccino
41
3.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
36
6.1k
Building Adaptive Systems
keathley
44
2.8k
Faster Mobile Websites
deanohume
310
31k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
132
19k
Statistics for Hackers
jakevdp
799
230k
Building Applications with DynamoDB
mza
96
6.8k
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?