Web App Performance
Performance in Context of the User
The RAIL Model
per·for·mance
/pərˈfôrməns/
noun
- The time it takes for actions to complete before the user's eyes
suc·cess
səkˈses/
noun
- 😁
Performance ROI
*Diagram courtesy of Google (Paul Lewis & Paul Irish), "Introduction to RAIL (Chrome Dev Summit 2015)"
⌛️⌛️⏳
⏳
😭
😁
The User's Perception
*Diagram courtesy of Google (Paul Lewis & Paul Irish), "Introduction to RAIL (Chrome Dev Summit 2015)"
RAIL Method:
An implementation-agnostic, high level, user-centric performance model
What is too slow?
What does the user feel?
User-centric Performance Goals
*Diagram courtesy of Google (Paul Lewis & Paul Irish), "Introduction to RAIL (Chrome Dev Summit 2015)"
esponse
R
A
I
L
Response
Applicable to inputs:
- buttons
- toggling form controls
- starting animations
Goal: Visual response to click/tap in < 100ms
Where the connection between action and reaction breaks.
Response
🛠 Implementation Strategies:
- Decouple the work from the visual response
- Provide feedback for actions > 500ms
- Instant visual response
- Feedback on status of the action
Response
🛠 Implementation Strategies:
- Optimistic UI's
- Assume Success
- Only delayed notifications are on server error
- Respond with errors within 2 seconds, while still in user's flow
Examples:
nimation
R
A
I
L
Animation
- Reasons why:
- A smooth experience
- Users notice frame rate variations
- The standard for most devices today
Goal: Each frame completes in less than 16ms (60 fps)
Animation
- The browser takes 6ms per frame to paint
- You get 10ms per frame
*Diagram courtesy of Google (Paul Lewis), "Rendering Performance" - developers.google.com
The Pixel Pipeline
Animation
🛠 Implementation Strategies:
**For animations experiencing sub-optimal performance:
- Avoid Animations that change the layout of the page
- Animate using transform and opacity
- Use will-change property - keeps browser from having to consider layout render or painting
.moving-element {
will-change: transform;
}
.moving-element {
transform: translateZ(0);
}
⚠️ Don't overuse
Animation
🛠 Implementation Strategies:
- Profiling animation performance
- Example of will-change and transform
dle
R
A
I
L
Idle Time
- Minimize data loading in pre-rendering stage
- Use idle time to load what you can
- Yield control back to main thread every 50ms, so the 100ms response goal can be met
Goal: Use idle time to do work, while still satisfying the Response goal
// Creating a new Web Worker
const myWorker = new Worker("./worker.js");
// Giving work to the worker thread
myWorker.postMessage(arbitraryObject);
console.log("Message posted to worker");
main.js
- Allows web content to run scripts in background threads
- Each script is a worker
- Can be used for processing, fetching, and storage interactions
Idle Time
Implementation Strategies (freeing up the main thread):
Web Workers
🛠
Idle Time
Web Workers
...
// Handling result event from worker thread
myWorker.onmessage = function(e) {
console.log(`
Message received from worker:
${JSON.stringify(e.data)}
`);
}
main.js
onmessage = function(e) {
console.log(`
Received object from the main script:
${JSON.stringify(e.data)}
`);
const workerResult = doWork(e.data);
// Posting a message back to main.js
postMessage(workerResult);
}
worker.js
Idle Time
Other Implementation Strategies (freeing up the main thread):
- Request Animation Frame (RAF)
- Async script tags
🛠
oad
R
A
I
L
Load
- User attention wanders after 1 second
Goal: First meaningful paint in < 1000ms
Load
🛠 Implementation Strategies
- Lazy loading - React Example:
class View extends React.Component {
...
// This method is invoked immediately after
// a component is mounted to the DOM.
componentDidMount() {
fetch("my-endpoint")
.then(data => data.json())
.then(json => this.setState({ pageData: json }))
.catch(e => this.setState({ pageError: e }));
}
render() {
return (
<MostMeaningfulContent />
{this.state.pageData && <SupplementalContent />}
)
}
}
Load
🛠 Implementation Strategies
- Server-side rendering and caching
- In-house options
- Community options
- Conditional loading:
- Load on scroll
- Load on event
🎊 The End 🎉
React Performance Techniques
Virtual DOM Defficiencies
shouldComponentUpdate()
React.PureComponent
shouldComponentUpdate(nextProps, nextState) {
return true;
}
shouldComponentUpdate()
Only needed if render() is a known bottleneck: - Requires hardcoding at the prop/state level - Has its own performance cost - Complicates the workflow (immutability)
DOM
Virtual DOM
<App />
<Header />
<Body />
New Props or State
render()
<Title />
<TileList />
<Tile />
<Tile />
<Tile />
render()
render()
render()
render()
render()
render()
render()
render()
Diff?
Update DOM()
Example
class Greeting extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const { props, state } = this;
if(
nextProps.name !== props.name
|| nextProps.hasVisited !== props.hasVisited
) {
// Update the component if a prop has changed
return true;
} else {
return false;
}
}
render() { // After initial render, only called if
// shouldComponentUpdate returns true
const { name, hasVisited } = this.props;
return <h1>Welcome {hasVisited && "back "}{name}!</h1>;
}
}
import Perf from 'react-addons-perf';
Demo with React Profilers
https://my-app.com?react_perf ➡
class Thing extends React.PureComponent {
...
}
React.PureComponent
class Thing extends React.PureComponent {
...
}
React.PureComponent
Only needed if render() is a known bottleneck:- Requires hardcoding at the prop level- Has its own performance cost - Complicates the workflow (immutability)
class Thing extends React.PureComponent {
...
}
React.PureComponent
===
class Thing extends React.Component {
...
shouldComponentUpdate(nextProps, nextState) {
let shouldUpdate = false;
Object.keys(nextProps).map(prop => {
if (nextProps[prop] !== this.props[prop]) {
// A prop has shallowly changed
shouldUpdate = true;
}
});
Object.keys(nextState).map(element => {
if (nextState[element] !== this.state[element]) {
// An element of state has shallowly changed
shouldUpdate = true;
}
});
return shouldUpdate;
}
}
Roadblocks
- Inline object literals as props
Roadblocks
- React.PureComponent for stateless components: 'recompose'
Classical Techniques
...Javascriptified
O(n^2) ➡ 🏌 ➡ O(nlogn) ➡ 🏌 ➡ O(n)
Time Complexity
Hashing Arrays By ID
// Turning an Array of objects into a hash for O(1) operations.
// Illustrated below: If the property being hashed by ('id' here)
// is not unique (like a primary key), previous records with the same
// id will be overwritten.
const myArr = [
{id: 1, data: 'a'},
{id: 1, data: 'b'},
{id: 2, data: 'c'}
];
const objectOfObjects = myArr.reduce((newHash, record) => {
newHash[record.id] = record;
return newHash;
}, {}) // <- {} is the 'newHash' seed
console.log(objectOfObjects);
Sources
-
RAIL Model
https://developers.google.com/web/fundamentals/performance/rail
https://www.smashingmagazine.com/2015/10/rail-user-centric-model-performance/
-
Web App Performance High Level Strategies
https://developers.google.com/web/fundamentals/performance/rendering/
https://www.smashingmagazine.com/2016/11/true-lies-of-optimistic-user-interfaces/
-
Web App Performance Techniques
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
-
Optimizing Web App Animations
https://developers.google.com/web/fundamentals/design-and-ux/animations/
https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance
https://medium.com/outsystems-experts/how-to-achieve-60-fps-animations-with-css3-db7b98610108
https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count
-
React Performance Techniques
Web App Performance Extended
By Evan Peterson
Web App Performance Extended
- 702