Slide 1

Slide 1 text

Rendering Performance #perfmatters Benoît Quenaudon @oldergod

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Google’s RAIL performance Model Response 100ms Animation 60fps Idle 50ms Load 1s

Slide 4

Slide 4 text

60fps ● Majority of device refresh screen 60 times per second ○ 1 frame = 16.6ms(1000ms ÷ 60) ● If frame is not created under 16ms, framerate drops ⇒ Jank happens ○ too much of it and you die

Slide 5

Slide 5 text

What is a Layer?

Slide 6

Slide 6 text

● JavaScript: work that will result in visual changes (not JavaScript only) ● Style: calculate the final styles for each element by figuring out which CSS rules apply to which elements. ● Layout (reflow): calculate dimensions and position for every element based on style ● Paint: filling in pixels for every layers ● Composite: draw all layers in the right order to the screen Pixel Pipeline

Slide 7

Slide 7 text

JS > Style > Layout > Paint > Composite ● What about performance? ○ Less work ⇒ Better performance ● Do we need to pass through all these jobs? ● JS / Style / Composite:Required ⇒ What about Layout and Paint? ○ Depends on the style

Slide 8

Slide 8 text

JS > Style > Layout > Paint > Composite Layout property ⇒ ● Width, height, position change can affect any element, the browser needs to re-Layout everything ● The affected area needs to be re-Paint

Slide 9

Slide 9 text

JS > Style > Paint > Composite Paint property ⇒ ● Color, shadow, background-image kind of change ● Nothing to do with the size nor dimension, Layout is skipped ● Still need to run Paint

Slide 10

Slide 10 text

JS > Style > Composite Composite property ⇒ ● Change at the layer level, nothing inside is changed     A reference for the render impact of mutating CSS properties:     https://csstriggers.com/

Slide 11

Slide 11 text

Use of the Pixel Pipeline at Our Advantage What can we do to improve performance? Each step of the pipeline in detail

Slide 12

Slide 12 text

JavaScript

Slide 13

Slide 13 text

Animation with JavaScript ● Usage of setTimeout or setInterval for animation is bad ○ We cannot predict their execution

Slide 14

Slide 14 text

Animation with JavaScript ● If setInterval > 60fps, overwork ● If setInterval < 60fps, jank ● If setInterval = 60fps, needs to be synced with the screen refresh timing ● There is still many 50Hz devices in Europe ⇒ requestAnimationFrame

Slide 15

Slide 15 text

Window.requestAnimationFrame() function step() { // Animation step, e.g. translateX += n; // Register a step for the next frame requestAnimationFrame(step); } // Register a step for the next frame requestAnimationFrame(step);

Slide 16

Slide 16 text

requestAnimationFrame() Support ● IE10 ≧ OK ● Others are fine ● Polyfill exists ⇒ No excuse not to use requestAnimationFrame

Slide 17

Slide 17 text

Main Thread (UI Thread) ● Job on the Main Thread: Style, Layout, Paint and JavaScript ● If all jobs combined take more than 16ms ⇒ Jank ● During JavaScript execution, all other jobs are blocked

Slide 18

Slide 18 text

Rendering VS JavaScript http://oldergod.github.io/ui-rendering-opt/demos/main-thread/index.html

Slide 19

Slide 19 text

Main Thread (UI Thread) ⇒ Enter Web Workers

Slide 20

Slide 20 text

Web Worker ➕ Runs on a different thread ➕ Does not affect rendering ➕ XMLHttpRequest, IndexedDB... OK ➖ No DOM access

Slide 21

Slide 21 text

Web Worker Demo http://oldergod.github.io/ui-rendering-opt/demos/web-workers/index.html

Slide 22

Slide 22 text

// worker registration const worker = new Worker('scripts/worker.js'); // Send message to the worker inside manipulateImage worker.postMessage({ type, imageData }); // Received a message from the worker worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker termination worker.terminate(); Web Worker Demo function onmessage(data); function postMessage(data); Web Worker Thread

Slide 23

Slide 23 text

// worker registration const worker = new Worker('scripts/worker.js'); // Send message to the worker inside manipulateImage worker.postMessage({ type, imageData }); // Received a message from the worker worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker termination worker.terminate(); Web Worker Demo function onmessage(data); function postMessage(data); Web Worker Thread

Slide 24

Slide 24 text

// worker registration const worker = new Worker('scripts/worker.js'); // Send message to the worker inside manipulateImage worker.postMessage({ type, imageData }); // Received a message from the worker worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker termination worker.terminate(); Web Worker Demo function onmessage(data); function postMessage(data); Web Worker Thread

Slide 25

Slide 25 text

// worker registration const worker = new Worker('scripts/worker.js'); // Send message to the worker inside manipulateImage worker.postMessage({ type, imageData }); // Received a message from the worker worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker termination worker.terminate(); Web Worker Demo function onmessage(data); function postMessage(data);

Slide 26

Slide 26 text

// worker registration const worker = new Worker('scripts/worker.js'); // Send message to the worker inside manipulateImage worker.postMessage({ type, imageData }); // Received a message from the worker worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker termination worker.terminate(); Web Worker Demo function onmessage(data); function postMessage(data); Web Worker Thread

Slide 27

Slide 27 text

Web Worker Support ● IE10 ≧ OK ● Others are fine ● Polyfill exists ⇒ No excuse not to use Web Workers

Slide 28

Slide 28 text

Layout

Slide 29

Slide 29 text

Complex Layout & Layout Thrashing ● Layout’s scope is usually the whole document ○ If only one element changes, all have to be re-Layout ○ The more elements, the higher cost ⇒ Then what? ○ Skip Layout by using Paint or Composite properties only ○ Audit with the DevTools to find bottlenecks

Slide 30

Slide 30 text

Layout Thrashing Demo Layout Thrashing: multiple forced synchronous layouts in quick succession http://oldergod.github.io/ui-rendering-opt/demos/layout-thrashing/index.html

Slide 31

Slide 31 text

Layout Thrashing Audit while (i--) { paragraphs[i].style.width = greenBar.offsetWidth + 'px'; }

Slide 32

Slide 32 text

Layout Thrashing Audit paragraphs[i + 1].style.width = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = greenBar.offsetWidth + 'px';

Slide 33

Slide 33 text

Layout Thrashing Audit offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 34

Slide 34 text

Layout Thrashing Audit offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 35

Slide 35 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 36

Slide 36 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 37

Slide 37 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 38

Slide 38 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 39

Slide 39 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; // Layout data had been invalidated so need to re-Layout offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 40

Slide 40 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; // Layout data had been invalidated so need to re-Layout offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;

Slide 41

Slide 41 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; // Layout data had been invalidated so need to re-Layout offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated, etc paragraphs[i].style.width = offsetWidth;

Slide 42

Slide 42 text

Layout Thrashing Audit // Usage of last calculated Layout data offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated paragraphs[i + 1].style.width = offsetWidth; // Layout data had been invalidated so need to re-Layout offsetWidth = greenBar.offsetWidth + 'px'; // Change in style ⇒ Layout data are invalidated, etc paragraphs[i].style.width = offsetWidth;

Slide 43

Slide 43 text

Layout Thrashing Fix while (i--) { paragraphs[i].style.width = greenBar.offsetWidth + 'px'; } let offsetWidth = greenBar.offsetWidth + 'px'; while (i--) { paragraphs[i].style.width = offsetWidth; }

Slide 44

Slide 44 text

Layout Thrashing ● Layout data are from last created frame ● If Style changes, Layout data are invalidated ⇒ Get Style & Layout only once, before changing anything ⇒ Let’s keep the Pixel Pipeline order in mind

Slide 45

Slide 45 text

Paint

Slide 46

Slide 46 text

Let’s keep Paint simple ● Paint is usually the most costly job in the pipeline

Slide 47

Slide 47 text

● By using multiple layers, we can tell the browser to only Paint the affected layer ⇒ What about Composite properties? Minimize Paint’s scope How do you create a Layer?

Slide 48

Slide 48 text

Composite

Slide 49

Slide 49 text

Composite Properties ● Position transform: translate(xpx, ypx); ● Scale transform: scale(n); ● Rotation transform: rotate(ndeg), ● Skew transform: skew(X|Y)(ndeg), ● Matrix transform: matrix(3d)(...), ● Opacity opacity: 0...1;

Slide 50

Slide 50 text

Composite only property ⚠ Condition:The affected element needs to be in its own layer to be able to skip Layout/Paint ⇒ Let’s promote this element to create its own Layer

Slide 51

Slide 51 text

How to make a Layer 1. Official: will-change: transform; 2. Hacky: transform: translateZ(0); ○ For when will-change is not supported

Slide 52

Slide 52 text

Composite Styling Demo http://oldergod.github.io/ui-rendering-opt/demos/layout-management/index.html

Slide 53

Slide 53 text

Use will-change: transform; or transform: translateZ(0); * { will-change: transform; transform: translateZ(0); } Element promotion // NG because of Management // and Memory cost

Slide 54

Slide 54 text

will-change Support ● IE、Edge (14): no-go ● Recent builds of other browsers: OK

Slide 55

Slide 55 text

Rendering Performance Wrapping up ● Always audit before optimize ● No need to fix something that is not broken ⇒ Always Audit First

Slide 56

Slide 56 text

Rendering Performance Wrapping down ● Less work ● Scheduled work ● Isolated work ● Smooze work ⇒ Happy User

Slide 57

Slide 57 text

Fin #perfmatters 格好良いエンジニア募集中! http://goo.gl/9GK409

Slide 58

Slide 58 text

Reference ● Google Web Fundamentals: Rendering Performance ○ https://developers.google.com/web/fundamentals/performance/rendering/ ● Udacity free course on Rendering Performance ○ https://www.udacity.com/course/browser-rendering-optimization--ud860 ● Repository of the demos ○ https://github.com/oldergod/ui-rendering-opt ● Render impact of mutating CSS properties reference ○ https://csstriggers.com/ ● Writing Efficient CSS ○ https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS ● BEM-like CSS ○ https://en.bem.info ● Cheap animation with FLIP ○ https://github.com/googlechrome/flipjs ● The RAIL Performance Model ○ https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail