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

Rendering Performance

Rendering Performance

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