Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Beyond 60 FPS
Chris Thoburn
@runspired
Ember + Mobile
@IsleOfCode
This is me, I recently took a timeout to wander a desert pondering the...
Metal
Today I want to talk to you about system metal. The guts.
This is how it feels to conquer the world and deliver an app that runs smoothly. Pedal to the Metal.
But too often it turns out like this cyclist. 

It turns out that it’s hard to build optimized, performance focused applic...
What is our metal?
And how much access do we have?
When I began focusing on performance, I began asking this question.

Do...
Doing Work Smarter
And we’re going to talk a lot about doing work smarter with requestAnimationFrame.

If you’ve never hea...
How does JS, JS?
But this isn’t a talk to tell you that requestAnimationFrame exists and why you should use it. Today we’l...
The Call Stack
For a lot of us, a stack trace such as this is our first experience with the call stack.
We entered the stack by calling aFunction, which called bFunction and so on until it hit the function that threw the error...
The Callback Queue
This is usually called the “Event Queue”, but in order to not confuse it with actual events, or a conce...
setTimeout(fn, 0);
This function is fancy magic. We know that Javascript is single threaded and that we can bump some work...
setTimeout(fnC, 0);
setTimeout(fnB, 0);
setTimeout(fnA, 0);
setTimeout(fnD, 0);
setTimeout(fnE, 0);
We might even use it t...
Let’s visualize. Let’s say we have a function “foo”.
And foo invokes setTimeout with the function bar.
Now, bar doesn’t immediately go onto the queue, instead it goes into this strange land of Web APIs, where a timer in a sep...
A timer kicks off, and at the right time, bar is sent to back of the queue.
And when the all the work from foo and from any other callbacks in front of this one completes, bar is invoked and becomes...
and we’re done!
Congrats, you know have a certificate in how JS works!
…but Wait!
requestAnimationFrame
How does requestAnimationFrame fit into this picture?
Ooof, there’s a lot going on in this picture, where to start.

For one, I’ve slyly renamed the callback queue to the “Macr...
this time, foo calls requestAnimationFrame instead of setTimeout, and our web APIs see this as a new FrameTask.
and they schedule the job (baz) into the next AnimationFrame, a separate callback queue.
foo calls requestAnimationFrame several times, and each time a new job is pushed into the next AnimationFrame callback que...
I. Finish The Call Stack
II. Check if should flush AnimationFrame
I. Flush AnimationFrame
III. Do next MacroTask from Macro...
What happens if you requestAnimationFrame
during an Animation Frame flush?
if you’ve ever used raf, you already know the an...
When does AnimationFrame flush?
The Window.requestAnimationFrame() method tells
the browser that you wish to perform an animation
and requests that the br...
If you’ve ever dug into Chrome’s “performance tuning” documentation, you may have seen this diagram.
How much can we do inside it?
raf flushes ~ every (1000 / 60 - X) ms
Where X is the amount of time spent in the previous AnimationFrame flush. This means ...
Style is separate

AnimationFrame’s budge will adjust

Here, we’re doing JS and Layout outside of the AnimationFrame, but ...
Awesome, let’s experiment with raf.
requestAnimationFrame and setTimeout cannot be used effectively together (you could schedule raf from the setTimeout callba...
What is requestAnimationFrame good for?
(besides animation?)
FRP style updates: this is a snippet from Hammer.js 3.0 which is currently under active development.

Here, we don’t sched...
This is a snippet from the next version of smoke-and-mirrors. This is a module that lets you add and remove scroll event h...
But instead of binding an event to scroll, it passively polls the element’s offset and triggers the callback when it has ch...
More accurate Throttling
Throttling or debouncing work that should only happen once per X renders.
A Better IntersectionObserver
radar, within smoke-and-mirrors, is basically a richer IntersectionObserver, I’ve been consi...
In poll mechanism for watching scroll, you may have noticed that I was flushing the callbacks inside the success callback o...
Promises
In Ember, we use a lot of promises for managing asynchronous behavior. How do they inter operate with setTimeout ...
We flushed our promise work before the console printed the return from our function. I promise you this is actually asynchr...
Expanding Our Understanding
(again)
I. Finish The Call Stack
II. Flush MicroTask Queue
III. Check if should flush Render MacroTasks
I. Flush Render MacroTasks
...
I. Promises
II. MessageChannel callbacks
III. MutationObserver callbacks
IV. setImmediate
MicroTask Implementations
A Kernel for the Web
So what’s this about a kernel for the web?

We want to get down to the system metal.
Houston, what’s our countdown at again?
We have a timing problem.

You do not know is the next macro task scheduled via se...
Forced Layouts are a timing problem.
Layout is a lot like a computed promise. Various events and actions will invalidate i...
JS => L => JS => L => JS => L => Paint => Composite
L: Layout
FL: Forced Layout
JS: Javascript Logic (app code)
P: Paint
C...
JS => L => JS => FL =>
JS => L => JS => FL =>
JS => L => Paint => Composite
L: Layout
FL: Forced Layout
JS: Javascript Log...
Increased asynchrony means decreased guarantees.
We do a lot of unnecessary work because of this.
I. (multiple) forced layouts
II. extraneous render flushes / diffs
III. mi...
requestAnimationFrame is not necessarily better
you can still force layout

you want to batch your DOM reads and your DOM ...
Scheduling work via MacroTasks is slow
and error prone.
setTimeout often takes 4-5ms to flush

we have very poor guarantees...
Scheduling work via MicroTasks is fast
but introduces order-of-operations issues.
We don’t know the order in which promise...
Timing Implications
- our app is doing extra work
- the browser is doing extra work
- we’re increasing the likelihood of a...
We are even further away from the “Metal” now.
(not that we were very good at these things before either)
The frameworks w...
Major and Minor Garbage Collection events
stop the world and have unpredictable timing, but they
do have predictable cause...
What we really want, is a way to schedule work

to happen at the optimal time for what it is.
Igniter
I want to introduce you to a project I’ve been building called Igniter.
Event Frame Render Frame Measure Frame Idle Frame
- sync

- actions

- cleanup
- render

- afterRender

- cleanup

- destr...
Event Frame
- sync

- actions

- cleanup
schedule(‘sync’, () => {});
EventFrame flushes as a micro task, and represents wor...
Render Frame
- render

- afterRender

- cleanup

- destroy (legacy)
Render flushes within RAF, specifically at the beginning...
Measure Frame
- measure

- affect
Measure also flushes within raf, but is guaranteed to flush after render. It itself is sepa...
Idle Frame
- gc

- query
if you know that an operation is going to cause a significant GC and can be deferred, schedule it ...
Mechanics
Primary Advantages
I. Align work correctly
II. Avoid duplicated App work
III. Ease the Browser’s Workload
IV. Meaningful S...
Secondary Advantages
requestIdleCallback polyfill
GC work

background network polling / worker polling / activity tracking / store management
requestLayoutFrame polyfill
Ultimately we shouldn’t be pushing layout into AnimationFrame like this, we need a new spec to ...
Streaming Templates
& Rendering Engines
Feature Potential
Smarter Animation Engines
Feature Potential
What is beyond 60fps?
Better CPU performance

Better Battery life

Even More Features
Is this what you were looking for?
Thanks :)
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Beyond 60fps
Upcoming SlideShare
Loading in …5
×

Beyond 60fps

The ways we currently schedule and perform work in applications are suboptimal. This talk takes a deep dive into how Javascript runtimes and browsers work, and how we can write code to make our app's do their work more intelligently. You'll walk away with deeper knowledge and appreciation for MicroTasks, MacroTasks, the call stack, and requestAnimationFrame, as well as a preview of Igniter, a kernel for the web.

  • Login to see the comments

Beyond 60fps

  1. 1. Beyond 60 FPS
  2. 2. Chris Thoburn @runspired Ember + Mobile @IsleOfCode This is me, I recently took a timeout to wander a desert pondering the true meaning of Javascript. This is the face I make when wondering what Tom Dale eats for breakfast.
  3. 3. Metal Today I want to talk to you about system metal. The guts.
  4. 4. This is how it feels to conquer the world and deliver an app that runs smoothly. Pedal to the Metal.
  5. 5. But too often it turns out like this cyclist. It turns out that it’s hard to build optimized, performance focused applications without access to system metal.
  6. 6. What is our metal? And how much access do we have? When I began focusing on performance, I began asking this question. Don’t worry, today isn’t going to be full of array or hash optimizations, it’s not about algorithms, Int32Arrays, Buffers, or Binary data. Today, we’re going to talk a lot about requestAnimationFrame.
  7. 7. Doing Work Smarter And we’re going to talk a lot about doing work smarter with requestAnimationFrame. If you’ve never heard of requestAnimationFrame, I suggest you google it once we’re off of conference wifi, you may also want to consider a new home that’s not this island, or a rock.
  8. 8. How does JS, JS? But this isn’t a talk to tell you that requestAnimationFrame exists and why you should use it. Today we’ll dive deeper into what it is, how it functions, and how you should be using it.
  9. 9. The Call Stack
  10. 10. For a lot of us, a stack trace such as this is our first experience with the call stack.
  11. 11. We entered the stack by calling aFunction, which called bFunction and so on until it hit the function that threw the error. Because invocation began with aFunction, the stack, and thus the trace, lead back to aFunction.
  12. 12. The Callback Queue This is usually called the “Event Queue”, but in order to not confuse it with actual events, or a concept we’ll introduce later called an “event frame”, we’re going skip on calling it that.
  13. 13. setTimeout(fn, 0); This function is fancy magic. We know that Javascript is single threaded and that we can bump some work down the line by wrapping it in this call.
  14. 14. setTimeout(fnC, 0); setTimeout(fnB, 0); setTimeout(fnA, 0); setTimeout(fnD, 0); setTimeout(fnE, 0); We might even use it to delay a lot of work. We get that there’s some queue of functions to be invoked, and that doing this pushed execution of a function to the end of that queue. How does this work?
  15. 15. Let’s visualize. Let’s say we have a function “foo”.
  16. 16. And foo invokes setTimeout with the function bar.
  17. 17. Now, bar doesn’t immediately go onto the queue, instead it goes into this strange land of Web APIs, where a timer in a separate process is going to deliver it back to us at the right time in case we say had called setTimeout with a number other than 0.
  18. 18. A timer kicks off, and at the right time, bar is sent to back of the queue.
  19. 19. And when the all the work from foo and from any other callbacks in front of this one completes, bar is invoked and becomes the start of a new call stack. There are some concepts here for debugging asynchronous code “stack stitching” “async trace”
  20. 20. and we’re done! Congrats, you know have a certificate in how JS works!
  21. 21. …but Wait!
  22. 22. requestAnimationFrame How does requestAnimationFrame fit into this picture?
  23. 23. Ooof, there’s a lot going on in this picture, where to start. For one, I’ve slyly renamed the callback queue to the “MacroTask” Queue. This just means these are higher level, but lower priority “jobs” or “tasks” that need to be done. We’ve got a few waiting. Off to the left, we’ve added a new purple box which we’ve called the Next AnimationFrame.
  24. 24. this time, foo calls requestAnimationFrame instead of setTimeout, and our web APIs see this as a new FrameTask.
  25. 25. and they schedule the job (baz) into the next AnimationFrame, a separate callback queue.
  26. 26. foo calls requestAnimationFrame several times, and each time a new job is pushed into the next AnimationFrame callback queue. How will this flush?
  27. 27. I. Finish The Call Stack II. Check if should flush AnimationFrame I. Flush AnimationFrame III. Do next MacroTask from MacroTask Queue (repeat) First, we complete the current call stack (all the work begun with foo). Next, we check if it’s time to flush the AnimationFrame queue, and if so, we flush it. Else, we do the next MacroTask, and repeat. We finish the callstack, check if we should flush the AnimationFrame, etc. Let’s see this in action.
  28. 28. What happens if you requestAnimationFrame during an Animation Frame flush? if you’ve ever used raf, you already know the answer, but let’s see this quickly too.
  29. 29. When does AnimationFrame flush?
  30. 30. The Window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes as an argument a callback to be invoked before the repaint. https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
  31. 31. If you’ve ever dug into Chrome’s “performance tuning” documentation, you may have seen this diagram.
  32. 32. How much can we do inside it?
  33. 33. raf flushes ~ every (1000 / 60 - X) ms Where X is the amount of time spent in the previous AnimationFrame flush. This means that if we take 10ms to flush, the next flush will begin only a few milliseconds later.
  34. 34. Style is separate AnimationFrame’s budge will adjust Here, we’re doing JS and Layout outside of the AnimationFrame, but it’s legal to do it within it as well.
  35. 35. Awesome, let’s experiment with raf.
  36. 36. requestAnimationFrame and setTimeout cannot be used effectively together (you could schedule raf from the setTimeout callback though)
  37. 37. What is requestAnimationFrame good for? (besides animation?)
  38. 38. FRP style updates: this is a snippet from Hammer.js 3.0 which is currently under active development. Here, we don’t schedule work into raf, we use raf to poll new state and flush it.
  39. 39. This is a snippet from the next version of smoke-and-mirrors. This is a module that lets you add and remove scroll event handlers for an element.
  40. 40. But instead of binding an event to scroll, it passively polls the element’s offset and triggers the callback when it has changed.
  41. 41. More accurate Throttling Throttling or debouncing work that should only happen once per X renders.
  42. 42. A Better IntersectionObserver radar, within smoke-and-mirrors, is basically a richer IntersectionObserver, I’ve been considering turning it into a polyfill + additional features.
  43. 43. In poll mechanism for watching scroll, you may have noticed that I was flushing the callbacks inside the success callback of a resolved promise. This begs a question.
  44. 44. Promises In Ember, we use a lot of promises for managing asynchronous behavior. How do they inter operate with setTimeout and raf?
  45. 45. We flushed our promise work before the console printed the return from our function. I promise you this is actually asynchronous, but don’t worry, I’ll explain.
  46. 46. Expanding Our Understanding (again)
  47. 47. I. Finish The Call Stack II. Flush MicroTask Queue III. Check if should flush Render MacroTasks I. Flush Render MacroTasks IV. Do next MacroTask from Event Queue (repeat) A fuller picture
  48. 48. I. Promises II. MessageChannel callbacks III. MutationObserver callbacks IV. setImmediate MicroTask Implementations
  49. 49. A Kernel for the Web So what’s this about a kernel for the web? We want to get down to the system metal.
  50. 50. Houston, what’s our countdown at again? We have a timing problem. You do not know is the next macro task scheduled via setTimeout will trigger before or after the next AnimationFrame flush. back burner (ember.run) flushes with setTimeout. Ember’s rendering engine flushes by scheduling into backburner’s render queue. This means, we do not know if our next render is before or after the next AnimationFrame flush.
  51. 51. Forced Layouts are a timing problem. Layout is a lot like a computed promise. Various events and actions will invalidate it, but it isn’t recomputed until it’s requested. Forced layouts happen when our JS code needs to read a layout related value and our layout is in an invalid state.
  52. 52. JS => L => JS => L => JS => L => Paint => Composite L: Layout FL: Forced Layout JS: Javascript Logic (app code) P: Paint C: Composite This is what the default state for most Ember apps is, multiple extra layouts Tying Ember’s render to setTimeout causes extra layouts.
  53. 53. JS => L => JS => FL => JS => L => JS => FL => JS => L => Paint => Composite L: Layout FL: Forced Layout JS: Javascript Logic (app code) P: Paint C: Composite Each time we did layout, we might also have needed to measure or alter something in the DOM, perhaps checking or modifying a class name, or maybe we wanted to know the new dimensions or location of an object. Now we additionally have multiple forced layouts to go with our extra layouts.
  54. 54. Increased asynchrony means decreased guarantees.
  55. 55. We do a lot of unnecessary work because of this. I. (multiple) forced layouts II. extraneous render flushes / diffs III. misaligned read/write operations IV. not all mutations and reads are equal
  56. 56. requestAnimationFrame is not necessarily better you can still force layout you want to batch your DOM reads and your DOM writes not all DOM writes are created equal. Abusing Frame MacroTasks can be as choppy and risky as setTimeout if we aren’t organized. Animation Frame’s flush stops the world, but we do not know when.
  57. 57. Scheduling work via MacroTasks is slow and error prone. setTimeout often takes 4-5ms to flush we have very poor guarantees of when
  58. 58. Scheduling work via MicroTasks is fast but introduces order-of-operations issues. We don’t know the order in which promises will be flushed. We can’t control batching reads and writes. Doing DOM work in a promise callback can quickly lead to Forced Layouts
  59. 59. Timing Implications - our app is doing extra work - the browser is doing extra work - we’re increasing the likelihood of a minor or a major GC event. - we don’t know if work we scheduled in RAF is happening before or after the render we care about.
  60. 60. We are even further away from the “Metal” now. (not that we were very good at these things before either) The frameworks we are building over have abstracted rendering, in the process, we have lost control we need.
  61. 61. Major and Minor Garbage Collection events stop the world and have unpredictable timing, but they do have predictable causes!
  62. 62. What we really want, is a way to schedule work to happen at the optimal time for what it is.
  63. 63. Igniter I want to introduce you to a project I’ve been building called Igniter.
  64. 64. Event Frame Render Frame Measure Frame Idle Frame - sync - actions - cleanup - render - afterRender - cleanup - destroy (legacy) - measure
 - affect - gc - query schedule(‘sync’, () => {}); Why do we defer work, debounce, throttle, schedule? - "do it at render" - "do it in the right order" - "do it at a better time" - "do it later, just not now" - "do this only once" Igniter tries to answer these questions.
  65. 65. Event Frame - sync - actions - cleanup schedule(‘sync’, () => {}); EventFrame flushes as a micro task, and represents work that should be done asynchronously but immediately and in a better order.
  66. 66. Render Frame - render - afterRender - cleanup - destroy (legacy) Render flushes within RAF, specifically at the beginning of RAF. This lets us avoid extra layouts and extra forced layouts that were present in the setTimeout version.
  67. 67. Measure Frame - measure
 - affect Measure also flushes within raf, but is guaranteed to flush after render. It itself is separated into “measure” and “affect”, think “read” and “write”.
  68. 68. Idle Frame - gc - query if you know that an operation is going to cause a significant GC and can be deferred, schedule it into the idle frame
  69. 69. Mechanics
  70. 70. Primary Advantages I. Align work correctly II. Avoid duplicated App work III. Ease the Browser’s Workload IV. Meaningful Stack Traces V. Easier Experimentation
  71. 71. Secondary Advantages
  72. 72. requestIdleCallback polyfill GC work background network polling / worker polling / activity tracking / store management
  73. 73. requestLayoutFrame polyfill Ultimately we shouldn’t be pushing layout into AnimationFrame like this, we need a new spec to bring us closer to the metal.
  74. 74. Streaming Templates & Rendering Engines Feature Potential
  75. 75. Smarter Animation Engines Feature Potential
  76. 76. What is beyond 60fps?
  77. 77. Better CPU performance Better Battery life Even More Features
  78. 78. Is this what you were looking for?
  79. 79. Thanks :)

×