Rendering Performance

Rendering Performance

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

September 24, 2016
Tweet

Transcript

  1. 2.
  2. 4.

    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
  3. 6.

    • 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
  4. 7.

    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
  5. 8.

    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
  6. 9.

    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
  7. 10.

    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/
  8. 11.

    Use of the Pixel Pipeline at Our Advantage What can

    we do to improve performance? Each step of the pipeline in detail
  9. 13.

    Animation with JavaScript • Usage of setTimeout or setInterval for

    animation is bad ◦ We cannot predict their execution
  10. 14.

    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
  11. 15.

    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);
  12. 16.

    requestAnimationFrame() Support • IE10 ≧ OK • Others are fine

    • Polyfill exists ⇒ No excuse not to use requestAnimationFrame
  13. 17.

    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
  14. 20.

    Web Worker ➕ Runs on a different thread ➕ Does

    not affect rendering ➕ XMLHttpRequest, IndexedDB... OK ➖ No DOM access
  15. 22.

    // 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. 23.

    // 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. 24.

    // 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
  18. 25.

    // 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);
  19. 26.

    // 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
  20. 27.

    Web Worker Support • IE10 ≧ OK • Others are

    fine • Polyfill exists ⇒ No excuse not to use Web Workers
  21. 28.
  22. 29.

    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
  23. 30.

    Layout Thrashing Demo Layout Thrashing: multiple forced synchronous layouts in

    quick succession http://oldergod.github.io/ui-rendering-opt/demos/layout-thrashing/index.html
  24. 32.

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

    paragraphs[i].style.width = greenBar.offsetWidth + 'px';
  25. 33.

    Layout Thrashing Audit offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i +

    1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
  26. 34.

    Layout Thrashing Audit offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i +

    1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
  27. 35.

    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;
  28. 36.

    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;
  29. 37.

    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;
  30. 38.

    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;
  31. 39.

    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;
  32. 40.

    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;
  33. 41.

    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;
  34. 42.

    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;
  35. 43.

    Layout Thrashing Fix while (i--) { paragraphs[i].style.width = greenBar.offsetWidth +

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

    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
  37. 45.
  38. 47.

    • 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?
  39. 48.
  40. 49.

    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;
  41. 50.

    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
  42. 51.

    How to make a Layer 1. Official: will-change: transform; 2.

    Hacky: transform: translateZ(0); ◦ For when will-change is not supported
  43. 53.

    Use will-change: transform; or transform: translateZ(0); * { will-change: transform;

    transform: translateZ(0); } Element promotion // NG because of Management // and Memory cost
  44. 55.

    Rendering Performance Wrapping up • Always audit before optimize •

    No need to fix something that is not broken ⇒ Always Audit First
  45. 56.

    Rendering Performance Wrapping down • Less work • Scheduled work

    • Isolated work • Smooze work ⇒ Happy User
  46. 58.

    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