Ember.JS Performance
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Penner, Stefan
One overarching theme in the Ember.js philosophy is to put developers on a path to success, primarily this is done by making that path have the lowest resistance. On several fronts this pattern has been quite successful, but on one important front we fall short, performance. This is the result of framework and run-time misalignment. This talk describes how we as a community can solve this problem in our apps and the framework.
 So I'm Stefan Penner. There isn't a hashtag of my name, that's just my IRC. As you may have noticed, I love open source software. I believe in continuous reinvestment, like our previous talk. I'm also a husband of a very patient wife, and she's actually here. So if everyone give her a round of applause. We have birds, and they like to pair program. One of them matches my color scheme. We did in fact choose the bird based on my color scheme. We have a new feathery friend. Unfortunately when I tried to pair with him, turns out he can like one shot headphones and power adapters so he can't pair with me. But he's lots of fun. He actually helped us navigate up to Portland. So turns out I accidentally washed my passport, can't fly here. Luckily I'm on this coast, so driving up from San Jose was actually quite fun. So announcement, I now work for Yahoo. They believe heavily in open source, and a large part of the Yahoo team runs some pretty massive Ember applications. This is pretty exciting for me. So at first I was kind of apprehensive. Yahoo, I think my first email address 17 years ago was at Yahoo. And I was pretty apprehensive. But being there, everyone is smart, that I interact with is smart, motivated, and excited, and it's really cool to see. So what am I doing at Yahoo? I'm fixing Ember CLI issues. This is awesome. I think my wife appreciates this, that I have a little bit more time to spend with her. And this is loads of fun. So Yahoo is hiring. If anyone wants to come join me and have some fun, we are riding the Canary way. We're going to be on the wave on as many applications as we can. Talk to me or talk to one of the other Yahoo's. Let's see if we can work something out. Anyway, so I love discussion. Feel free to ask me questions. But we went a little bit over before. So if I cut you off, that just means bug me afterwards. So there's a warning. Browser internals are like French to me. I can understand a few things. I can say a few things. I can ask where the washroom is. I can ask you to put it on the toilet. It is very useful. But I might be wrong. So this talk, we're going to talk about some performance myth, mechanical sympathy, and then Ember's misalignments. So we all know JavaScript performance is all about how you enumerate arrays. And just in case you're curious, those are all different ways of enumerating arrays. That's it. Talk's done. Turns out Tom and Yehuda fixed all the problems, all the HTML bar guys fixed all the problems. We're done. Thank you. So although we're neck and neck with React, I think we can do a little bit better. So up until somewhat recently, this is what JavaScript performance looked like to me. I think to lots of you as well. Until I started getting involved with promises and there was lots of complaints about how slow they were, but they provide this fantastic mechanism by which we can abstract some complexity. Promises were really slow. There was actually a blog post by Spion who enumerated these problems. And then all of a sudden, there was another blog post by Spion saying, I'm switching to promises. And basically, it was because of this library called Bluebird, promise implementation. And Bluebird's author, Petka Antonov, basically without changing the external API, made promises at least an order of magnitude faster. So at this point, I'm like, okay, I had done work on RSVP and I was like, how on earth has he accomplished this? So I expected when I opened the Bluebird box to find 12 weird checks to double your JavaScript performance. When I opened the box after some investigation, what I say next might shock you. What if I told you promises using normal, get this, normal for loops were actually fast? I love this picture. I've been like carrying it from talk to talk. It's fantastic. So basically, the TLDR of why Bluebird was faster is it did less work. You want something to go faster, make it do less work. The question often is, what is the work? So in the Bluebird case, it was two things, allocate less objects and then underlie, align with the underlying primitive. So figure out what JavaScript runtimes are doing and try to not fight them but work with them. And now promises are fast. But the most important part is this was done without an external API change. A wild ember appears. Turns out we care about making things faster without breaking externalize or minimizing external API breakages. So this is very appealing. Stability without stagnation. And boom, you guys saw this pretty awesome demo which I was a little bit nervous about since I'm giving a performance talk after that. But I had early access and I spun up the profiler and I saw these things. And I said, ah, this is something React doesn't have, we can do better. So even with this item poten re-render branch, the ceiling I believe is still pretty high. We have a lot of fun work left to do and we can get way more performance out than we have even with that branch. How much faster can we go? Who knows? So there's another part and that is when we're developing, we actually want to be able to develop faster. We just don't want to deal with esoteric ways of making things faster. We also want to be productive. And it turns out the same way as we confuse ourselves is the same way as we confuse the runtime. And often realizing what the runtime is doing under the hood might actually encourage good programming patterns. So turns out by reducing the confusion, we can actually make ember even faster than it is today. So who here played Pokemon? Who here was able to clone rare candies? Well, for those that don't know what that is, there was a glitch. And it allowed you to clone items in your inventory. So there's this thing called rare candy which would allow you to level up a Pokemon. But you could use this glitch to get many rare candies and level up your Pokemon really quickly. Unfortunately, as you leveled up your Pokemons, if you allowed them to evolve too early, you would miss out on some of their important skills. So as we work on ember, we have to be sure that we're not jumping the gun and we're not throwing things away that are actually good in favor of something that looks a little bit better but actually might lead us down a wrong path. So we have some important choices to make. But it's often not clear which choices we can make. So as ember evolves, how can we make a choice that both is productive for developers and doesn't screw us when it comes to performance? So often I feel this is JavaScript performance. Who here doesn't know what cargo culling is? Well, anyways, South Pacific Islanders around the World War II era recognized that there was a correlation between the arrival of cargo and things that look like runways and planes and people working. They just missed. They weren't quite sure why that was happening actually. But so they tried to emulate this and they just couldn't understand why sometimes cargo would arrive and sometimes it wouldn't. Let's be honest, a lot of times in JavaScript performance, we make the same choices. We often hear X is slow, don't do X. We fixate on the X and we fixate on not to use it. But as we know, anyone that's, yes, X is slow is the wrong thing to say. The more important question is when is X slow? Not just that, when is X used? Relative magnitude matters. Sometimes a very slow thing will actually enable less allocations or less work being done. So the better question is when is X reasonable and when is it not reasonable? But to make this choice, we have to understand what X is without missing the connection. We end up trying something and then we end up with Mr. A. Liv's performance calculus. Two patches, 5% improvement, together 10% regression. And we have done this countless times in Ember. I am at fault for lots of them. I am sure everyone who has committed to Ember has contributed to this problem. And we need to fix this. So there is this cool term that I picked up from Martin Thompson of Elmax. Elmax is a high-performance trading platform. And they achieved their high performance not by scaling out, but by understanding the underlying hardware and the underlying runtime. He stole this idea from a dude called Jackie Stewart, who is a three-time F1 champ. Basically the best drivers had the understanding of how the machine worked so that they could work in harmony with it. Rather than working against the runtimes, we can probably work with them, get a little bit more oomph out of them. So by correctly aligning the cars with the driver, the driver with the car, you are able to utilize the car to its full capacity. It turns out F1 cars, V8 engines, same problem. Yes, they have V6s, but whatever. And don't worry, although I'm going to be talking a lot about V8, these common paradigms and patterns really hold true across most of the runtimes. So the world's simplest example, A plus B. I stole this from Petka. I'm sorry, but it's a great example. So what V8 does is it looks at that source code and it immediately emits some assembly. This assembly is fast, but could be faster, but it's extraordinarily safe because we can be adding two integers together, two numbers together, two objects together. We don't know. But the important part here is when it generates this code, it also adds hooks to record the type information for later specialization. Then as code gets hot, the type information is consulted to see, hey, can we specialize further? So basically, if a function for A and B is used often and A and B are always 32-bit integers, we can specialize. How does this happen in V8? There's an optimizing compiler called Cragshaft that keeps track of the control flow, portable assembly, and then emits native assembly for your particular runtime. How does this actually work? This awesome diagram by Mr. Aleph. This comes in, we compile something that's quick to compile, has these hooks, as stuff gets faster, we consult the optimizing compiler, which gives us new native code that is more efficient. Ultimately, after the optimizing compiler is done with it, we end up with ideally something like this, which is many times faster, but maybe more brittle. So to review, AST comes in. We first generate something that is safe, not specialized. As it's used, we can generate more optimized code for that case, mad science. So how does this work? Basically, types and stability and mad science. But Steph, I do more than just arithmetic. Well, it turns out there's these concept of shapes in the runtime, and they allow us to take these same types of optimizations and apply them to the rest of our code. So if we look at this ES6 class, or ES2015 class, it is a shape of, in this case, two slots, a first name slot and a last name slot. Here we have a rect, not a react, that also has two slots, but these two shapes are entirely different, just like an integer is different than a string, same thing. So when we go in and instantiate a few of these rects, we have one shape, but we have multiple instances. Great. So remember this thing where if we knew the type information of A and B, we could specialize? Well, if we have an add heights method that takes A and B, rect A and rect B and adds them together, it's awfully similar. So as the dynamic portions are used, as the function gets hot, type information is recorded, and if we're always dealing with the same shapes, and those shapes always have the same numbers or values, rather than having to do a very expensive property lookup, we can ideally know the offset of the integer, and we end up with, hopefully, something as close as this in the ideal search area. So what this boils down to is if the runtime believes the code is stable and predictable, it's able to make things faster. As also as it turns out, well-factored code that humans can understand and read often has these characteristics. There's another aspect to performance, and that is the time versus space tradeoff. So the previous stuff was time-based. Can we make something execute faster? And the second one is space. So space takes up space, but also space takes up time. Allocations take time. Cleanup the garbage collector in a garbage collected language takes a huge amount of time. And in Ember apps, this is a big place where we are causing lots of problems. Things that cost us in the GC and space world, closures, objects, unoptimized code. So it turns out unoptimized code allocates more objects. Turns out even compile code allocates extra objects, and if we have too many different shapes, the functions have to be specialized too many times, and we end up with compile code actually causing more garbage collector problems than our app data, which is just bonkers. So what we need to do with Ember is we need to do less work, allocate less, and also align us with the underlying primitives. I have an expectation that reasonable code should be reasonably fast. In some places, there's a bug in the runtime that we can get them to fix. In other cases, we are just doing unreasonable things, and we need to fix them. So I have three misalignments that Ember does today that we're going to cover. So the first one is, Ember just does way too much work. The solution is do less work. What work exactly? Well, we saw a great example of this in the earlier talk. HTML bars and item potenry render mitigate a whole category of extra work. This is fantastic. The other thing is, as we're moving to Ember 2.0, we are suggesting people use actions up and bindings down. It turns out that explicit data flow is also less work. We don't have all these two-way bindings everywhere, potentially wasteful, potentially not even used, or even worse. Often applications, I see that we're relying on like sloshing between two properties that are bound. Actually happens and no one even noticed it. We're just burning time that we don't even want or don't even need. And another part of this doing too much work is singleton controllers make this problem even worse. We really have an explicit life cycle. When we transition routes, we're causing change events that we don't care about. When I leave the user route and go to, let's say, the settings route and I'm tearing down information from the user's controller, it doesn't know that we don't care about it anymore. It actually propagates the same change events. This is exceptionally costly. As we move to routable components and explicit state, we will just be able to delete the component. This is much less than if we allow it to live and be live bound still. So this basically means as we make Ember clear, our app will get faster. And this is absolutely the case. So the second misalignment is in a knit and super. A knit and super, I believe, is exceptionally hard to learn. Even I like to think I know a few things about Ember. And I'm often surprised. Who has tried to subclass an array proxy. It turns out you have to do things in very strange orders. But it's because we're misaligned with how super should actually be working. This causes us to allocate extra shapes, which makes the runtime not be able to specialize correctly. It results in more compile code that we're not going to use and just a big mess. And it's not quite aligned with what the new specifications class and super objects have. The solution here is to embrace super. So who's written code like this before? We have first name and last name defined on the objects. No one's written code like this before? Everyone in this room that has written Ember app before has written code this way. And it turns out that these shapes here, they come and sneak up and they hurt us here. Because what's actually happening is we create a new person. We create two new persons. We have the person proto, which represents the class on the left. We have the person shape and then two instances. They are of the same shape. But when we go to set full name, it turns out that first name and last name were actually set on the slots of the parent. We set first name and last name. We're actually creating a whole new shape potentially. Sometimes V8 and sometimes runtimes are smart enough to catch this. But as we have more and more properties, we run the risk of creating multiple different shapes, which just means that code that should actually all be dealing with the same person shape might have to deal with any different combinations and permutations of the person shape, which is not cool. Also it is not clear to define these properties on the prototype, especially if someone defines an array on the prototype and all instances share them. So the solution to this, I believe, is to change init and to change super so that we actually pass args in default args. We just use JavaScript to see, hey, was args on first name set? If not, let's set Joe. And then we'll call super. And at the root super, we'll actually set the properties to the instance. What we do right now is we actually set the properties before init is called. So there's no way for you to know if there's a default property or if the property came in via create or new. So another cool thing of this, before, right now, there's actually no set of rules that will define how to use super. With this change, there's two rules. We call super if we are overriding a framework method and before we touch the this in the method. And taking that, moving forward, that is all you will need to know about how to use super. So not only is this easier to explain, teach, and understand, but it's more aligned with the underlying runtime. Thoughts? Anyone? Please show me. I'm excited. Misalignment number three. And this one is rough, but I believe we have a solution. So basically, Ember object reopen is super powerful. It allows Ember to slowly augment objects as you pull in new features. So if you add in the router, you can add features. It also is a great way for us to add deprecations and to pull out existing parts of Ember and store them as add-ons, but mix them back in so that we allow people to fall back to old functionality. But it turns out it is kind of buggy. It requires complex internals and causes mass, is the root cause for massive amounts of our allocations and shapes. Ember is basically, I would like to propose that we limit reopen to only work until the object has its first instantiation. So remember that chart I showed of Tom and Yehuda's branch? Basically the most expensive things that are left are related to this lazy reopen code. So who here doesn't know what meta is in Ember? Come on, put up your hands. Who's seen it but doesn't know what it is? All right, that sounds a little bit better. So although meta and actions 4 are very related, meta is a good thing, I think. We can fix it. This is meta. It's the heart of everything that powers Ember. It's how the listeners, watchers, events, CPs, bindings, chains, everything works via these meta things. And in a normal application, this is kind of every class has a meta, every instance has a meta, and the meta is inherit the same way the class hierarchy inherits. It turns out that most of these metas are fine, but the metas for the instances are what kill us. The flexibility that we need to support reopen after the first instantiation basically means that we are grinding against the underlying runtimes. So meta is basically for live inheriting. It does some crazy things that we can totally change. Basically every O create is a new shape. If we create 4,000 records, Ember data users basically, we'll have 4,000 different metas, meaning any code that touches meta, which is all of the super sensitive code, can't possibly be optimized. It turns out that if we can get rid of this reopen after first instantiation, we can actually make every meta the same shape, and we can now potentially benefit from actual runtime optimizations in these code paths. And one trick that allows us to do this is no one inherits from the meta on an instance. No one makes a subclass of the user instance. They maybe make a subclass of the user class, but this little detail will allow us to shed even more of that performance problems that we saw. We thought meta was bad. It turns out actions for, which is heavily entangled with meta, is a little bit crazy. I don't know. Who here has run into this code before? I don't know. You look at it, you're like, well, I don't want to deal with this. But as it turns out, applying these same rules, we can totally make meta listeners substantially faster. And how does this actually work? Each meta, which already has some problems, has another object called listeners, which has all of the problems that meta has and more. This is kind of the graph of the Ember object. But as you can see, the meddas and the listeners in green, they're pretty fine because we have few classes in a system. We maybe have hundreds of them, but we can have thousands and thousands of instances. So if we can optimize those last ones, those last pieces of profile, go away. So solution. We will work with V8 to handle this better. I believe we will kill, hopefully, maybe not. Hopefully we can kill reopen after instantiation. We will optimize meta's for instantiation and listeners for subscription. So I have an early spike of this. I can't demo it to you because it is not quite ready. But the early numbers are substantially faster than what we have today. So I'm quite hopeful of this. And if anyone wants to help, Yahoo is hiring. Come see me or come see any of the other Yahoo's and love to work with you. So when doing optimizations, it turns out premature optimization is not necessarily a great thing. We should only do it when it actually matters. We should only do it through the code that will actually provide us performance. But it's fun. Yeah, yeah, yeah. Another thing that I have learned is that benchmarks can trick you. Not only can they trick you, they will almost always certainly trick you. The only way to have reasonable certainty in your improvements are using all the tooling available to you and work with someone else that can nitpick your work. And remember, actions up and bindings down. That's it. Thank you. Any questions? Sounds very reasonable as well. Thank you.
 So I'm Stefan Penner.  There isn't a hashtag of my name, that's just my IRC.  As you may have noticed, I love open source software.  I believe in continuous reinvestment, like our previous talk.  I'm also a husband of a very patient wife, and she's actually here.  So if everyone give her a round of applause.  We have birds, and they like to pair program.  One of them matches my color scheme.  We did in fact choose the bird based on my color scheme.  We have a new feathery friend.  Unfortunately when I tried to pair with him, turns out he can like one shot headphones  and power adapters so he can't pair with me.  But he's lots of fun.  He actually helped us navigate up to Portland.  So turns out I accidentally washed my passport, can't fly here.  Luckily I'm on this coast, so driving up from San Jose was actually quite fun.  So announcement, I now work for Yahoo.  They believe heavily in open source, and a large part of the Yahoo team runs some pretty  massive Ember applications.  This is pretty exciting for me.  So at first I was kind of apprehensive.  Yahoo, I think my first email address 17 years ago was at Yahoo.  And I was pretty apprehensive.  But being there, everyone is smart, that I interact with is smart, motivated, and excited,  and it's really cool to see.  So what am I doing at Yahoo?  I'm fixing Ember CLI issues.  This is awesome.  I think my wife appreciates this, that I have a little bit more time to spend with her.  And this is loads of fun.  So Yahoo is hiring.  If anyone wants to come join me and have some fun, we are riding the Canary way.  We're going to be on the wave on as many applications as we can.  Talk to me or talk to one of the other Yahoo's.  Let's see if we can work something out.  Anyway, so I love discussion.  Feel free to ask me questions.  But we went a little bit over before.  So if I cut you off, that just means bug me afterwards.  So there's a warning.  Browser internals are like French to me.  I can understand a few things.  I can say a few things.  I can ask where the washroom is.  I can ask you to put it on the toilet.  It is very useful.  But I might be wrong.  So this talk, we're going to talk about some performance myth, mechanical sympathy, and  then Ember's misalignments.  So we all know JavaScript performance is all about how you enumerate arrays.  And just in case you're curious, those are all different ways of enumerating arrays.  That's it.  Talk's done.  Turns out Tom and Yehuda fixed all the problems, all the HTML bar guys fixed all the problems.  We're done.  Thank you.  So although we're neck and neck with React, I think we can do a little bit better.  So up until somewhat recently, this is what JavaScript performance looked like to me.  I think to lots of you as well.  Until I started getting involved with promises and there was lots of complaints about how  slow they were, but they provide this fantastic mechanism by which we can abstract some complexity.  Promises were really slow.  There was actually a blog post by Spion who enumerated these problems.  And then all of a sudden, there was another blog post by Spion saying, I'm switching to  promises.  And basically, it was because of this library called Bluebird, promise implementation.  And Bluebird's author, Petka Antonov, basically without changing the external API, made promises  at least an order of magnitude faster.  So at this point, I'm like, okay, I had done work on RSVP and I was like, how on earth  has he accomplished this?  So I expected when I opened the Bluebird box to find 12 weird checks to double your JavaScript  performance.  When I opened the box after some investigation, what I say next might shock you.  What if I told you promises using normal, get this, normal for loops were actually fast?  I love this picture.  I've been like carrying it from talk to talk.  It's fantastic.  So basically, the TLDR of why Bluebird was faster is it did less work.  You want something to go faster, make it do less work.  The question often is, what is the work?  So in the Bluebird case, it was two things, allocate less objects and then underlie, align  with the underlying primitive.  So figure out what JavaScript runtimes are doing and try to not fight them but work with  them.  And now promises are fast.  But the most important part is this was done without an external API change.  A wild ember appears.  Turns out we care about making things faster without breaking externalize or minimizing  external API breakages.  So this is very appealing.  Stability without stagnation.  And boom, you guys saw this pretty awesome demo which I was a little bit nervous about  since I'm giving a performance talk after that.  But I had early access and I spun up the profiler and I saw these things.  And I said, ah, this is something React doesn't have, we can do better.  So even with this item poten re-render branch, the ceiling I believe is still pretty high.  We have a lot of fun work left to do and we can get way more performance out than we have  even with that branch.  How much faster can we go?  Who knows?  So there's another part and that is when we're developing, we actually want to be able to  develop faster.  We just don't want to deal with esoteric ways of making things faster.  We also want to be productive.  And it turns out the same way as we confuse ourselves is the same way as we confuse the  runtime.  And often realizing what the runtime is doing under the hood might actually encourage good  programming patterns.  So turns out by reducing the confusion, we can actually make ember even faster than it  is today.  So who here played Pokemon?  Who here was able to clone rare candies?  Well, for those that don't know what that is, there was a glitch.  And it allowed you to clone items in your inventory.  So there's this thing called rare candy which would allow you to level up a Pokemon.  But you could use this glitch to get many rare candies and level up your Pokemon really  quickly.  Unfortunately, as you leveled up your Pokemons, if you allowed them to evolve too early, you  would miss out on some of their important skills.  So as we work on ember, we have to be sure that we're not jumping the gun and we're not  throwing things away that are actually good in favor of something that looks a little  bit better but actually might lead us down a wrong path.  So we have some important choices to make.  But it's often not clear which choices we can make.  So as ember evolves, how can we make a choice that both is productive for developers and  doesn't screw us when it comes to performance?  So often I feel this is JavaScript performance.  Who here doesn't know what cargo culling is?  Well, anyways, South Pacific Islanders around the World War II era recognized that there  was a correlation between the arrival of cargo and things that look like runways and planes  and people working.  They just missed.  They weren't quite sure why that was happening actually.  But so they tried to emulate this and they just couldn't understand why sometimes cargo  would arrive and sometimes it wouldn't.  Let's be honest, a lot of times in JavaScript performance, we make the same choices.  We often hear X is slow, don't do X.  We fixate on the X and we fixate on not to use it.  But as we know, anyone that's, yes, X is slow is the wrong thing to say.  The more important question is when is X slow?  Not just that, when is X used?  Relative magnitude matters.  Sometimes a very slow thing will actually enable less allocations or less work being  done.  So the better question is when is X reasonable and when is it not reasonable?  But to make this choice, we have to understand what X is without missing the connection.  We end up trying something and then we end up with Mr. A. Liv's performance calculus.  Two patches, 5% improvement, together 10% regression.  And we have done this countless times in Ember.  I am at fault for lots of them.  I am sure everyone who has committed to Ember has contributed to this problem.  And we need to fix this.  So there is this cool term that I picked up from Martin Thompson of Elmax.  Elmax is a high-performance trading platform.  And they achieved their high performance not by scaling out, but by understanding the underlying  hardware and the underlying runtime.  He stole this idea from a dude called Jackie Stewart, who is a three-time F1 champ.  Basically the best drivers had the understanding of how the machine worked so that they could  work in harmony with it.  Rather than working against the runtimes, we can probably work with them, get a little  bit more oomph out of them.  So by correctly aligning the cars with the driver, the driver with the car, you are able  to utilize the car to its full capacity.  It turns out F1 cars, V8 engines, same problem.  Yes, they have V6s, but whatever.  And don't worry, although I'm going to be talking a lot about V8, these common paradigms  and patterns really hold true across most of the runtimes.  So the world's simplest example, A plus B. I stole this from Petka.  I'm sorry, but it's a great example.  So what V8 does is it looks at that source code and it immediately emits some assembly.  This assembly is fast, but could be faster, but it's extraordinarily safe because we can  be adding two integers together, two numbers together, two objects together.  We don't know.  But the important part here is when it generates this code, it also adds hooks to record the  type information for later specialization.  Then as code gets hot, the type information is consulted to see,  hey, can we specialize further?  So basically, if a function for A and B is used often and A and B are always 32-bit integers,  we can specialize.  How does this happen in V8?  There's an optimizing compiler called Cragshaft that keeps track of the control flow, portable  assembly, and then emits native assembly for your particular runtime.  How does this actually work?  This awesome diagram by Mr. Aleph.  This comes in, we compile something that's quick to compile, has these hooks, as stuff  gets faster, we consult the optimizing compiler, which gives us new native code that is more  efficient.  Ultimately, after the optimizing compiler is done with it, we end up with ideally something  like this, which is many times faster, but maybe more brittle.  So to review, AST comes in.  We first generate something that is safe, not specialized.  As it's used, we can generate more optimized code for that case, mad science.  So how does this work?  Basically, types and stability and mad science.  But Steph, I do more than just arithmetic.  Well, it turns out there's these concept of shapes in the runtime, and they allow us to  take these same types of optimizations and apply them to the rest of our code.  So if we look at this ES6 class, or ES2015 class, it is a shape of, in this case, two  slots, a first name slot and a last name slot.  Here we have a rect, not a react, that also has two slots, but these two shapes are entirely  different, just like an integer is different than a string, same thing.  So when we go in and instantiate a few of these rects, we have one shape, but we have  multiple instances.  Great.  So remember this thing where if we knew the type information of A and B, we could specialize?  Well, if we have an add heights method that takes A and B, rect A and rect B and adds  them together, it's awfully similar.  So as the dynamic portions are used, as the function gets hot, type information is recorded,  and if we're always dealing with the same shapes, and those shapes always have the same  numbers or values, rather than having to do a very expensive property lookup, we can ideally  know the offset of the integer, and we end up with, hopefully, something as close as  this in the ideal search area.  So what this boils down to is if the runtime believes the code is stable and predictable,  it's able to make things faster.  As also as it turns out, well-factored code that humans can understand and read often  has these characteristics.  There's another aspect to performance, and that is the time versus space tradeoff.  So the previous stuff was time-based.  Can we make something execute faster?  And the second one is space.  So space takes up space, but also space takes up time.  Allocations take time.  Cleanup the garbage collector in a garbage collected language takes a huge amount of  time.  And in Ember apps, this is a big place where we are causing lots of problems.  Things that cost us in the GC and space world, closures, objects, unoptimized code.  So it turns out unoptimized code allocates more objects.  Turns out even compile code allocates extra objects, and if we have too many different  shapes, the functions have to be specialized too many times, and we end up with compile  code actually causing more garbage collector problems than our app data, which is just  bonkers.  So what we need to do with Ember is we need to do less work, allocate less, and also align  us with the underlying primitives.  I have an expectation that reasonable code should be reasonably fast.  In some places, there's a bug in the runtime that we can get them to fix.  In other cases, we are just doing unreasonable things, and we need to fix them.  So I have three misalignments that Ember does today that we're going to cover.  So the first one is, Ember just does way too much work.  The solution is do less work.  What work exactly?  Well, we saw a great example of this in the earlier talk.  HTML bars and item potenry render mitigate a whole category of extra work.  This is fantastic.  The other thing is, as we're moving to Ember 2.0, we are suggesting people use actions  up and bindings down.  It turns out that explicit data flow is also less work.  We don't have all these two-way bindings everywhere, potentially wasteful, potentially  not even used, or even worse.  Often applications, I see that we're relying on like sloshing between two properties that  are bound.  Actually happens and no one even noticed it.  We're just burning time that we don't even want or don't even need.  And another part of this doing too much work is singleton controllers make this problem  even worse.  We really have an explicit life cycle.  When we transition routes, we're causing change events that we don't care about.  When I leave the user route and go to, let's say, the settings route and I'm tearing down  information from the user's controller, it doesn't know that we don't care about it anymore.  It actually propagates the same change events.  This is exceptionally costly.  As we move to routable components and explicit state, we will just be able to delete the  component.  This is much less than if we allow it to live and be live bound still.  So this basically means as we make Ember clear, our app will get faster.  And this is absolutely the case.  So the second misalignment is in a knit and super.  A knit and super, I believe, is exceptionally hard to learn.  Even I like to think I know a few things about Ember.  And I'm often surprised.  Who has tried to subclass an array proxy.  It turns out you have to do things in very strange orders.  But it's because we're misaligned with how super should actually be working.  This causes us to allocate extra shapes, which makes the runtime not be able to specialize  correctly.  It results in more compile code that we're not going to use and just a big mess.  And it's not quite aligned with what the new specifications class and super objects  have.  The solution here is to embrace super.  So who's written code like this before?  We have first name and last name defined on the objects.  No one's written code like this before?  Everyone in this room that has written Ember app before has written code this way.  And it turns out that these shapes here, they come and sneak up and they hurt us here.  Because what's actually happening is we create a new person.  We create two new persons.  We have the person proto, which represents the class on the left.  We have the person shape and then two instances.  They are of the same shape.  But when we go to set full name, it turns out that first name and last name were actually  set on the slots of the parent.  We set first name and last name.  We're actually creating a whole new shape potentially.  Sometimes V8 and sometimes runtimes are smart enough to catch this.  But as we have more and more properties, we run the risk of creating multiple different  shapes, which just means that code that should actually all be dealing with the same person  shape might have to deal with any different combinations and permutations of the person  shape, which is not cool.  Also it is not clear to define these properties on the prototype, especially if someone defines  an array on the prototype and all instances share them.  So the solution to this, I believe, is to change init and to change super so that we  actually pass args in default args.  We just use JavaScript to see, hey, was args on first name set?  If not, let's set Joe.  And then we'll call super.  And at the root super, we'll actually set the properties to the instance.  What we do right now is we actually set the properties before init is called.  So there's no way for you to know if there's a default property or if the property came  in via create or new.  So another cool thing of this, before, right now, there's actually no set of rules that  will define how to use super.  With this change, there's two rules.  We call super if we are overriding a framework method and before we touch the this in the  method.  And taking that, moving forward, that is all you will need to know about how to use super.  So not only is this easier to explain, teach, and understand, but it's more aligned with  the underlying runtime.  Thoughts?  Anyone?  Please show me.  I'm excited.  Misalignment number three.  And this one is rough, but I believe we have a solution.  So basically, Ember object reopen is super powerful.  It allows Ember to slowly augment objects as you pull in new features.  So if you add in the router, you can add features.  It also is a great way for us to add deprecations and to pull out existing parts of Ember and  store them as add-ons, but mix them back in so that we allow people to fall back to old  functionality.  But it turns out it is kind of buggy.  It requires complex internals and causes mass, is the root cause for massive amounts of our  allocations and shapes.  Ember is basically, I would like to propose that we limit reopen to only work until the  object has its first instantiation.  So remember that chart I showed of Tom and Yehuda's branch?  Basically the most expensive things that are left are related to this lazy reopen code.  So who here doesn't know what meta is in Ember?  Come on, put up your hands.  Who's seen it but doesn't know what it is?  All right, that sounds a little bit better.  So although meta and actions 4 are very related, meta is a good thing, I think.  We can fix it.  This is meta.  It's the heart of everything that powers Ember.  It's how the listeners, watchers, events, CPs, bindings, chains, everything works via  these meta things.  And in a normal application, this is kind of every class has a meta, every instance  has a meta, and the meta is inherit the same way the class hierarchy inherits.  It turns out that most of these metas are fine, but the metas for the instances are  what kill us.  The flexibility that we need to support reopen after the first instantiation basically means  that we are grinding against the underlying runtimes.  So meta is basically for live inheriting.  It does some crazy things that we can totally change.  Basically every O create is a new shape.  If we create 4,000 records, Ember data users basically, we'll have 4,000 different metas,  meaning any code that touches meta, which is all of the super sensitive code, can't  possibly be optimized.  It turns out that if we can get rid of this reopen after first instantiation, we can actually  make every meta the same shape, and we can now potentially benefit from actual runtime  optimizations in these code paths.  And one trick that allows us to do this is no one inherits from the meta on an instance.  No one makes a subclass of the user instance.  They maybe make a subclass of the user class, but this little detail will allow us to shed  even more of that performance problems that we saw.  We thought meta was bad.  It turns out actions for, which is heavily entangled with meta, is a little bit crazy.  I don't know.  Who here has run into this code before?  I don't know.  You look at it, you're like, well, I don't want to deal with this.  But as it turns out, applying these same rules, we can totally make meta listeners substantially  faster.  And how does this actually work?  Each meta, which already has some problems, has another object called listeners, which  has all of the problems that meta has and more.  This is kind of the graph of the Ember object.  But as you can see, the meddas and the listeners in green, they're pretty fine because we have  few classes in a system.  We maybe have hundreds of them, but we can have thousands and thousands of instances.  So if we can optimize those last ones, those last pieces of profile, go away.  So solution.  We will work with V8 to handle this better.  I believe we will kill, hopefully, maybe not.  Hopefully we can kill reopen after instantiation.  We will optimize meta's for instantiation and listeners for subscription.  So I have an early spike of this.  I can't demo it to you because it is not quite ready.  But the early numbers are substantially faster than what we have today.  So I'm quite hopeful of this.  And if anyone wants to help, Yahoo is hiring.  Come see me or come see any of the other Yahoo's and love to work with you.  So when doing optimizations, it turns out premature optimization is not necessarily a great thing.  We should only do it when it actually matters.  We should only do it through the code that will actually provide us performance.  But it's fun.  Yeah, yeah, yeah.  Another thing that I have learned is that benchmarks can trick you.  Not only can they trick you, they will almost always certainly trick you.  The only way to have reasonable certainty in your improvements are using all the tooling  available to you and work with someone else that can nitpick your work.  And remember, actions up and bindings down.  That's it.  Thank you.  Any questions?  Sounds very reasonable as well.  Thank you.
Ember_0_203.png Ember_204_311.png Ember_312_803.png Ember_804_1271.png Ember_1272_1763.png Ember_1764_2543.png Ember_2544_3431.png Ember_3432_3767.png Ember_3768_5207.png Ember_5208_6599.png Ember_6600_6947.png Ember_6948_7007.png Ember_7008_7259.png Ember_7260_8747.png Ember_8748_9035.png Ember_9036_9167.png Ember_9168_9419.png Ember_9420_10295.png Ember_10296_11543.png Ember_11544_12731.png Ember_12732_12875.png Ember_12876_13235.png Ember_13236_13427.png Ember_13428_14123.png Ember_14124_14591.png Ember_14592_15407.png Ember_15408_15599.png Ember_15600_19871.png Ember_19872_20483.png Ember_20484_22343.png Ember_22344_23879.png Ember_23880_24131.png Ember_24132_27107.png Ember_27108_27539.png Ember_27540_27659.png Ember_27660_33119.png Ember_33120_37547.png Ember_37548_38687.png Ember_38688_38891.png Ember_38892_38975.png Ember_38976_39346.png