Rendering Performance

Rendering Performance

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

September 24, 2016
Tweet

Transcript

  1. Rendering Performance #perfmatters Benoît Quenaudon @oldergod

  2. None
  3. Google’s RAIL performance Model Response 100ms Animation 60fps Idle 50ms

    Load 1s
  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
  5. What is a Layer?

  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
  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
  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
  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
  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/
  11. Use of the Pixel Pipeline at Our Advantage What can

    we do to improve performance? Each step of the pipeline in detail
  12. JavaScript

  13. Animation with JavaScript • Usage of setTimeout or setInterval for

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

    • Polyfill exists ⇒ No excuse not to use requestAnimationFrame
  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
  18. Rendering VS JavaScript http://oldergod.github.io/ui-rendering-opt/demos/main-thread/index.html

  19. Main Thread (UI Thread) ⇒ Enter Web Workers

  20. Web Worker ➕ Runs on a different thread ➕ Does

    not affect rendering ➕ XMLHttpRequest, IndexedDB... OK ➖ No DOM access
  21. Web Worker Demo http://oldergod.github.io/ui-rendering-opt/demos/web-workers/index.html

  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
  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
  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
  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);
  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
  27. Web Worker Support • IE10 ≧ OK • Others are

    fine • Polyfill exists ⇒ No excuse not to use Web Workers
  28. Layout

  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
  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
  31. Layout Thrashing Audit while (i--) { paragraphs[i].style.width = greenBar.offsetWidth +

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

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

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

    1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
  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;
  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;
  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;
  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;
  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;
  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;
  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;
  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;
  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; }
  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
  45. Paint

  46. Let’s keep Paint simple • Paint is usually the most

    costly job in the pipeline
  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?
  48. Composite

  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;
  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
  51. How to make a Layer 1. Official: will-change: transform; 2.

    Hacky: transform: translateZ(0); ◦ For when will-change is not supported
  52. Composite Styling Demo http://oldergod.github.io/ui-rendering-opt/demos/layout-management/index.html

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

    transform: translateZ(0); } Element promotion // NG because of Management // and Memory cost
  54. will-change Support • IE、Edge (14): no-go • Recent builds of

    other browsers: OK
  55. Rendering Performance Wrapping up • Always audit before optimize •

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

    • Isolated work • Smooze work ⇒ Happy User
  57. Fin #perfmatters 格好良いエンジニア募集中! http://goo.gl/9GK409

  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