Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rendering Performance

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Rendering Performance

Avatar for Benoît Quenaudon

Benoît Quenaudon

September 24, 2016
Tweet

More Decks by Benoît Quenaudon

Other Decks in Programming

Transcript

  1. 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
  2. • 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
  3. 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
  4. 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
  5. 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
  6. 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/
  7. Use of the Pixel Pipeline at Our Advantage What can

    we do to improve performance? Each step of the pipeline in detail
  8. Animation with JavaScript • Usage of setTimeout or setInterval for

    animation is bad ◦ We cannot predict their execution
  9. 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
  10. 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);
  11. requestAnimationFrame() Support • IE10 ≧ OK • Others are fine

    • Polyfill exists ⇒ No excuse not to use requestAnimationFrame
  12. 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
  13. Web Worker ➕ Runs on a different thread ➕ Does

    not affect rendering ➕ XMLHttpRequest, IndexedDB... OK ➖ No DOM access
  14. // 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
  15. // 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
  16. // 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
  17. // 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);
  18. // 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
  19. Web Worker Support • IE10 ≧ OK • Others are

    fine • Polyfill exists ⇒ No excuse not to use Web Workers
  20. 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
  21. Layout Thrashing Demo Layout Thrashing: multiple forced synchronous layouts in

    quick succession http://oldergod.github.io/ui-rendering-opt/demos/layout-thrashing/index.html
  22. Layout Thrashing Audit paragraphs[i + 1].style.width = greenBar.offsetWidth + 'px';

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

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

    1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
  25. 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;
  26. 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;
  27. 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;
  28. 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;
  29. 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;
  30. 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;
  31. 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;
  32. 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;
  33. Layout Thrashing Fix while (i--) { paragraphs[i].style.width = greenBar.offsetWidth +

    'px'; } let offsetWidth = greenBar.offsetWidth + 'px'; while (i--) { paragraphs[i].style.width = offsetWidth; }
  34. 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
  35. • 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?
  36. 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;
  37. 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
  38. How to make a Layer 1. Official: will-change: transform; 2.

    Hacky: transform: translateZ(0); ◦ For when will-change is not supported
  39. Use will-change: transform; or transform: translateZ(0); * { will-change: transform;

    transform: translateZ(0); } Element promotion // NG because of Management // and Memory cost
  40. Rendering Performance Wrapping up • Always audit before optimize •

    No need to fix something that is not broken ⇒ Always Audit First
  41. Rendering Performance Wrapping down • Less work • Scheduled work

    • Isolated work • Smooze work ⇒ Happy User
  42. 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