beyond 60fps

94
Beyond 60 FPS

Upload: chris-thoburn

Post on 24-Jan-2018

514 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Beyond 60fps

Beyond 60 FPS

Page 2: Beyond 60fps

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.

Page 3: Beyond 60fps

Metal

Today I want to talk to you about system metal. The guts.

Page 4: Beyond 60fps

This is how it feels to conquer the world and deliver an app that runs smoothly. Pedal to the Metal.

Page 5: Beyond 60fps

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.

Page 6: Beyond 60fps

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.

Page 7: Beyond 60fps

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.

Page 8: Beyond 60fps

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.

Page 9: Beyond 60fps

The Call Stack

Page 10: Beyond 60fps

For a lot of us, a stack trace such as this is our first experience with the call stack.

Page 11: Beyond 60fps

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.

Page 12: Beyond 60fps

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.

Page 13: Beyond 60fps

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.

Page 14: Beyond 60fps

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?

Page 15: Beyond 60fps

Let’s visualize. Let’s say we have a function “foo”.

Page 16: Beyond 60fps

And foo invokes setTimeout with the function bar.

Page 17: Beyond 60fps

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.

Page 18: Beyond 60fps

A timer kicks off, and at the right time, bar is sent to back of the queue.

Page 19: Beyond 60fps

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”

Page 20: Beyond 60fps

and we’re done!

Congrats, you know have a certificate in how JS works!

Page 21: Beyond 60fps

…but Wait!

Page 22: Beyond 60fps

requestAnimationFrame

How does requestAnimationFrame fit into this picture?

Page 23: Beyond 60fps

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.

Page 24: Beyond 60fps

this time, foo calls requestAnimationFrame instead of setTimeout, and our web APIs see this as a new FrameTask.

Page 25: Beyond 60fps

and they schedule the job (baz) into the next AnimationFrame, a separate callback queue.

Page 26: Beyond 60fps

foo calls requestAnimationFrame several times, and each time a new job is pushed into the next AnimationFrame callback queue.

How will this flush?

Page 27: Beyond 60fps

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.

Page 28: Beyond 60fps
Page 29: Beyond 60fps
Page 30: Beyond 60fps
Page 31: Beyond 60fps
Page 32: Beyond 60fps
Page 33: Beyond 60fps
Page 34: Beyond 60fps

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.

Page 35: Beyond 60fps
Page 36: Beyond 60fps
Page 37: Beyond 60fps
Page 38: Beyond 60fps
Page 39: Beyond 60fps

When does AnimationFrame flush?

Page 40: Beyond 60fps

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

Page 41: Beyond 60fps

If you’ve ever dug into Chrome’s “performance tuning” documentation, you may have seen this diagram.

Page 42: Beyond 60fps

How much can we do inside it?

Page 43: Beyond 60fps

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.

Page 44: Beyond 60fps

Style is separateAnimationFrame’s budge will adjustHere, we’re doing JS and Layout outside of the AnimationFrame, but it’s legal to do it within it as well.

Page 45: Beyond 60fps

Awesome, let’s experiment with raf.

Page 46: Beyond 60fps
Page 47: Beyond 60fps
Page 48: Beyond 60fps

requestAnimationFrame and setTimeout cannot be used effectively together (you could schedule raf from the setTimeout callback though)

Page 49: Beyond 60fps

What is requestAnimationFrame good for?

(besides animation?)

Page 50: Beyond 60fps

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.

Page 51: Beyond 60fps

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.

Page 52: Beyond 60fps

But instead of binding an event to scroll, it passively polls the element’s offset and triggers the callback when it has changed.

Page 53: Beyond 60fps

More accurate Throttling

Throttling or debouncing work that should only happen once per X renders.

Page 54: Beyond 60fps

A Better IntersectionObserver

radar, within smoke-and-mirrors, is basically a richer IntersectionObserver, I’ve been considering turning it into a polyfill + additional features.

Page 55: Beyond 60fps

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.

Page 56: Beyond 60fps

Promises

In Ember, we use a lot of promises for managing asynchronous behavior. How do they inter operate with setTimeout and raf?

Page 57: Beyond 60fps
Page 58: Beyond 60fps

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.

Page 59: Beyond 60fps

Expanding Our Understanding

(again)

Page 60: Beyond 60fps
Page 61: Beyond 60fps

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

Page 62: Beyond 60fps

I. Promises II. MessageChannel callbacks III. MutationObserver callbacks

IV. setImmediate

MicroTask Implementations

Page 63: Beyond 60fps

A Kernel for the Web

So what’s this about a kernel for the web?

We want to get down to the system metal.

Page 64: Beyond 60fps

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.

Page 65: Beyond 60fps

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.

Page 66: Beyond 60fps

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.

Page 67: Beyond 60fps

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.

Page 68: Beyond 60fps

Increased asynchrony means decreased guarantees.

Page 69: Beyond 60fps

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

Page 70: Beyond 60fps

requestAnimationFrame is not necessarily better

you can still force layoutyou want to batch your DOM reads and your DOM writesnot all DOM writes are created equal.

Abusing Frame MacroTasks can be as choppy and risky as setTimeoutif we aren’t organized.

Animation Frame’s flush stops the world,but we do not know when.

Page 71: Beyond 60fps

Scheduling work via MacroTasks is slow and error prone.

setTimeout often takes 4-5ms to flushwe have very poor guarantees of when

Page 72: Beyond 60fps

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

Page 73: Beyond 60fps

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.

Page 74: Beyond 60fps

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 abstractedrendering, in the process, we have lost control we need.

Page 75: Beyond 60fps

Major and Minor Garbage Collection events

stop the world and have unpredictable timing, but they do have predictable causes!

Page 76: Beyond 60fps

What we really want, is a way to schedule work

to happen at the optimal time for what it is.

Page 77: Beyond 60fps

Igniter

I want to introduce you to a project I’ve been building called Igniter.

Page 78: Beyond 60fps

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.

Page 79: Beyond 60fps

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.

Page 80: Beyond 60fps

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.

Page 81: Beyond 60fps

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

Page 82: Beyond 60fps

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

Page 83: Beyond 60fps

Mechanics

Page 84: Beyond 60fps
Page 85: Beyond 60fps

Primary Advantages

I. Align work correctly

II. Avoid duplicated App work III. Ease the Browser’s Workload IV. Meaningful Stack Traces

V. Easier Experimentation

Page 86: Beyond 60fps

Secondary Advantages

Page 87: Beyond 60fps

requestIdleCallback polyfill

GC workbackground network polling / worker polling / activity tracking / store management

Page 88: Beyond 60fps

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.

Page 89: Beyond 60fps

Streaming Templates

& Rendering Engines

Feature Potential

Page 90: Beyond 60fps

Smarter Animation Engines

Feature Potential

Page 91: Beyond 60fps

What is beyond 60fps?

Page 92: Beyond 60fps

Better CPU performanceBetter Battery lifeEven More Features

Page 93: Beyond 60fps

Is this what you were looking for?

Page 94: Beyond 60fps

Thanks :)