Speed, Speed, Speed: JavaScript vs C++ vs WebAssembly

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so my name is Francisca and I talk about JavaScript performance javascript compiler specifically v8 because I used to be on the v8 team and that's the compiler and nodejs an electron and chrome of course so we'll look at performance of modern JavaScript how it compares to performance of C++ and then very briefly we're web assembly fits into this performance discussion now why would you care about the compiler ideally it's just there compiling your code and you don't have to worry about it and also you really should never write code specifically for compilers to get certain performance obviously this doesn't work on a browser at all because you can't control what browsers your clients are running but even for server-side applications for node and electron do not cater specifically to the compiler because what the compiler is doing internally performance wise it's not part of the JavaScript specification it's not in a contract in the API it can change at any time I mean v8 is updated every six weeks and even between those six weeks they're they're patches and fixes so if you have anything that you wrote because you thought it's faster if you do it this way it can change anytime and it's just not what you should be doing so why would you care about learning some of the internals and the abstract concepts of modern JavaScript engines well having an idea of what's going on under the hood will make you a better developer you can think about it kind of like driving a car anybody can learn how to drive a car without needing to know anything about the engine if your car's broken or for inspection you take it somewhere and they fix it and you just drive your car and it's all good but if you want to be a professional racecar driver or a really good driver and bad weather conditions it will be helpful if you have some idea of what's going on under the hood in the engine so it's a little bit similar with JavaScript having some idea about it doesn't mean you have to like dig into it and make a big mess and then like be slower the next time we push another v8 release it just means you have better understanding and will help you eventually when you have performance issues or when you try to figure out what's going on okay so the talk is titled speed speed feet and we're talking about performance I want to point out I'm talking only about the runtime performance so the runtime performance of JavaScript C++ and web assembly I'm not talking about all the other things that usually make your app slow so I'm not talking about IO a data basis or HTTP or rendering or different frameworks like those are usually the big things you can target when you need to improve performance there's also usually the things that really slow stuff down we're just looking at how long does it take to run javascript code not sending anything over the wire not having other bottlenecks just the JavaScript code itself and then a big warning never ever blindly optimize you just create a ton of bugs and make it hard to maintain your code this should be obvious but I want to say it anyways don't blindly optimize always measure first make sure you even have a performance issue like yeah make sure you have a performance issue that you want to address and then measure and figure out where your bottleneck is don't just say oh I heard this talk I learned some stuff about compilers I'm going to make every function monomorphic no no no because that's gonna give you such a tiny speed-up if at all probably not even because compilers are complicated like don't go and optimize unless you've measured and you'd figured out what you need to optimize all right before we can talk about performance I want to point out one fundamental difference between dynamically scripted language a dynamically typed scripting language like JavaScript and a statically typed scripting language statically typed language like C++ not a scripting language okay so in JavaScript if you want to have a property X on an object you can do this at any time you say define a variable object assign it sign an object to it and then set this property X to 40 to perfectly valid code can you do the same thing in C++ you pretty much can accept C++ compilers are a little stricter and you first have to tell the C++ compiler what the tybor the shape of object is so you first have to define a class where you tell the compiler this class has a field that's public and it's an integer and it's called X okay and then once you have this class declaration just like in JavaScript you can say object object and set to property X 242 not nothing really special but now in JavaScript if you've changed your mind if other data is coming in you can always say oh I also need this Y field on my object it has value 17 if the same change of mine happens to you in C++ compiler is gonna be like no no no I'm not this is not working like this right so anybody who's written any C++ before you always have to figure out what the object type is define your class and then you can use it you cannot just randomly add properties or delete them like you can do in JavaScript and because you can change the types all the time like this in JavaScript it's called a dynamically typed language it's not static like C++ you can change things dynamically as you need them say you're getting JSON over the wire and you want to put this JSON into an object you don't have to first up say oh it's going to have these properties and now assigned adjacent to it no you just take the JSON and it's dynamically creating these object types um that can be really nice when you do work with things like Jason it's also very nice when you're just learning a language when you're prototyping something but for a compiler it's a lot of work because you're not telling the compiler upfront what will happen so in fact if you do something simple like a property look up in JavaScript and obviously you do object dot X all the time like just to cancel that lock you're looking up the log property on console like it's all over your code you can't live without it but there's a lot of things that can happen when you have this tiny little dot for a property look up in your code what happens if you do this you could get a type error X could be undefined X could be undefined on object but be defined further up on a prototype chain X could be be a proxy but the get trap X could have been defined with an axis or descriptors you have and I get function which can do whatever side effects you wanted to do and even if X is defined on the object and the compiler still doesn't know where to find the value of x and the memory layout of object because the compiler doesn't have a blueprint of this object if it's c++ the compiler knows okay object belongs to the class object and this is the memory layout and it's never going to change javascript compiler sadly doesn't know a lot so there's a lot of things that can happen for something as simple as this and in fact in the ECMO scripts specification so don't worry about reading all this I macroscope specifies how JavaScript has to be implemented what should happen and so just a simple property look up if eight steps here sorry making sure everybody's awake so in the specification something very simple like a property lookup is a ton of steps and in fact each one of those steps here is another long list of ten steps or so so everything's really complicated in JavaScript it's not just oh you get this property and you're done no no there's like lots of things well if it's undefined if it's on a prototype chain if it's a proxy if it's a get Travis one so if everything if just if like the tiniest little bit of work as a ton of work for JavaScript compiler what's our intuition about JavaScript performance it's this JavaScript performance well instead of intuition let's look at some data so I was calculating some prime numbers and I did it in C++ and JavaScript exactly the same I won't go rhythm and I think this slide is super amazing like yes javascript is slower than C++ here but this is a linear scale and JavaScript is less than effect a to slower like that's kind of the same speed right like yes it's a little bit slower but it's not exponentially slower or anything you can calculate the millions prime number and it would take you eight seconds with my terrible C++ algorithm but the same terrible JavaScript algorithm only takes 15 seconds it's not that much worse okay you're shaking your head it's twice as slow but it's it's actually really good if you go back to this other slide where you think about how much work it is for a compiler that should just get a property of an object this is absolutely amazing so how can modern JavaScript be this fast the trick that all modern engines use is just in time compilation just-in-time compilation sometimes called lazy compilation often abbreviated as JIT compilation you can turn it into a warp and say it's this code jittered so it's a just-in-time compiled and that means JavaScript compilers only compile the code as you random just-in-time they're not compiling everything ahead of time generating and executable that you don't run it's compiling just in time as you need to run some code so your typical compiler 2 will change you always start with some source code obviously and then the compiler or the engine has a positive this human readable source code and turns it into an abstract syntax tree and ast so that's the different representation of your source code that's more suitable for Judis to reach than the actual source code and then the interpreter or compiler depends on what language you talk about in the context the interpreter or compiler then takes this abstract syntax tree and generates machine code and you can run this machine code on so that's your typical compiler tool chain now for just-in-time compilers it's not a one-way tool chain anymore instead it's a loop here where the compiler generates some machine code a little bit of machine code is run and then we go back and compile more code into machine code so there's this constant alternation between compile-time and run-time so we're compiling a little bit we're running it and then we go back to compiling some more and running more how is this faster how is this a plus this just sounds really messy and like you would have a ton of overhead for going back and forth all the time well the big trick here is by running some of the code the compiler can gather information from runtime some runtime feedback so this is information that the compiler doesn't see by looking at the source code or the abstract syntax tree this is information it only gets from running the code and then this runtime feedbag is used in the next compilation step that's the big trick why switching back and forth between compiling and runtime can actually speed up JavaScript even though it sounds way more complicated and saying compile everything run everything by switching it up the compiler can get information from runtime and use that to generate faster machine code the next time it's compiling so JavaScript compilers they use feedback collected at runtime to speed up the performance okay and that's actually not enough to get this amazing performance where we're almost as fast as C++ since 2011 chrome was the first one and then a few years later the other major engines also added add they all have at least two compilers they have a baseline compiler and then they have at least one optimising compiler so Safari doesn't have one optimising pilot has two optimizing compilers even and this optimizing compiler it recompiles hot functions so a function that is being run a lot that could pass to the optimizing compiler and when a compiles machine code for heart function it uses runtime information it speculates that the future of inputs will be similar to what it saw in the past it's making a speculation about what data it will get and it's optimizing the machine code for that and if it turns out that the speculation was wrong that the future is not the same as what it saw in the past then it will just de optimize and fall back to the baseline compiler alright so that was very abstract let's look at this more concretely with code so if this really complicated function he has an example of function add takes an object returns one plus the X property of this object everybody clear on that function right okay so in our touching here we have two source code and then the positons this add function into an abstract syntax tree when we run the function and now the baseline compiler generates machine code for this add function and in my example I'm calling add a few times and I'm always calling it with a regular object that only has a field X that has an integer value like it's an object there's nothing special about the prototype chain it doesn't have any other fields than then X and the value of x is always different numbers it's not the same number like it's not always that the answer is 42 even though that's the answer to everything it's not the same number but it's always small integers and it's always the same shape of an object that we're passing in to add okay so I'm running this function many times so at some point this function is considered hot because we get this feedback from the runtime hey this add function is being executed a lot so the function is hot and the optimising compiler takes over and it recompiles the source code for add but now it can use the information the speculation that we will call this function always with an object that's a regular object with nothing special on the prototype chain that has an X property and nothing so we're speculating that the future input will look similar to the inputs in the past and so this machine code that comes out runs much faster than the one on the left but it only runs if this speculation is right now of course in JavaScript it's totally legit to call a function a thousand times with similar inputs and then at some point change your mind and say oops changed my mind calling this add function with a different object now um this does not give you wrong JavaScript it's not that your code will crash or anything it's just for the compiler this optimized machine code will give you the wrong result if you were to run it with this kind of input so instead of running the optimized input the optimized machine code the compiler D compiles and passes on this input to the machine code that was generated by the baseline compiler so it's the slower version but it's correct for this kind of input okay and so here's actually the x86 assembly code that's being generated by the aid for this little odd function that I showed you and the left-hand side is what's generated by the baseline compiler it runs on for a few more pages I couldn't fit everything on here but these four lines on the right hand side is what's generated by the optimizing compiler and this is both for the same ad function it's does add function takes an object returns one plus object at X and obviously every instruction takes some time so if you have lines and lines of instructions it's way way slower than just four lines of instructions and this might look very cryptic but it's actually not that hard especially because we know what this little add function is doing so if we look at these four lines of code they're the first line is cmp compare this is where we are comparing that the input matches what we were speculating so we have this hidden class in the background in v8 where we sort of write down what an object shape looks like and we're just comparing that the new input matches what we've seen in the past because only for that kind of input is the optimized code right so this is a little check or guard before we can use the optimized code and then the next slide line is JM jump that's we're jumping bail out so we are bailing out to the D opt to the baseline compiler if the comparison didn't hold so if you're trying to run the optimized code and the input didn't match the speculation that the compiler made in the beginning if the if the input is not of that shape with just an X with an integer then this optimized code is jumping it's bailing out to these long list of instructions on the left if our speculation does hold though then we only have two more lines to run morph is a move instruction this is where we're moving the value of X to another register and this is a simple move instruction because we speculated on the shape of the input so the compiler actually knows the memory layout off it so it's easy to access the x value and then the last one at that's just addition where we're adding one to that value of x so this is how those four lines translate to the add function but if you can't make that speculation in the beginning that you have a certain kind of input then you're stuck doing several hundred instructions on the left and as I said doing hundreds of instructions are slower than four instructions so you can actually run the eights of node and Chrome an electron with a flag where you say - there's no optimization and that's your upper picture so before javascript engines had a second optimizing compiler you were stuck with the upper picture where now C++ is way faster than JavaScript and you can't say oh this is amazing we're almost at native speed no no if you don't have the optimizing compiler now it takes you eighty seconds instead of ten seconds but if you just use the optimizing compiler like the default then you're pretty much in the same ballpark with JavaScript as with native C++ and then this one this slide is amazing to me because you can see the optimizing compiler in the data so here i zoomed into the first 200 iterations and at first javascript is a lot slower than c++ right if you look from 0 to 50 years so it's a lot slower and then it actually spikes a ton like it's not even on the graph anymore that is where the optimizing compiler is recompiling your source code because obviously recompiling something also takes time so you don't get it for free and that's what's why we can't optimize everything all the time because it's a trade-off between is it worth optimizing this function but we're counting the same function here over and over again and the prime number calculation so it's considered hot and it is being optimized and then around the hundreds prime number that's when you see in the data that the optimising compiler has finished compiling and we're now running the optimized machine code so you can literally see where's the warmup period for the optimizing compiler when it's done compiling and you see that when you measure how long it takes to run it if you want to play around with any of that if you use node you can just do - - and then the v8 options for example trace optimization or trace the optimization that will list which function is being optimized and when is it being T optimized if you use Chrome you just do - - Jas Flags equals and then use these flags you can also print the assembly code if you want to see them and there's like a really long list of more options either check the source code or like ping me on Twitter and they give it to you there's a bunch of websites of repos that actually just list them ok all right so I was comparing JavaScript to C++ and we saw that modern JavaScript is actually pretty good compared to native C++ if you do need more speed than JavaScript if you really do have something that's computationally intensive and you cannot deal with it that it's twice as slow that javascript is still twice as slow as C++ if you write node or an electron app then you can write native add-ons so that is code written and C++ but you use it like a JavaScript module so users of your library don't have to worry that this is C++ code that you give them they will just do require module and then they just call whatever function they want from your API some popular examples that you might be using already as notes ass or GRP see a lot of things that work with encrypt cryptography like bcrypt and also sequel light 3 they all use native add-ons but when you use it you don't need to know any c++ or you're not using the modules any different than you would use a JavaScript module and obviously you can use native modules and electron works very similar to when you just use them and note and if you want to play with any native modules we have it on the official note jester dork documentation the hello world and a few other things and it's it's really not that hard to write a native module there's two things that you need you need a build file for jib a binding the jib file that gives some instructions which C++ file to use and then in your C++ code that you have anyways for the functionality that you want to implement you just need a little bit of boilerplate C++ because in C++ you kind of need to wire together the French name in JavaScript to the method name and C++ I think it's not all magic but it's really just boilerplate code so this is the sample build file the important part here is that we're saying used to fire prime stat CC and put the output into primes you need to give names to things and you also need to tell the build 5water your C++ sources and then in the C++ code so this is my prime set c c we have the prime number algorithm at the top there's just a little bit of boilerplate here so in this function here we are wiring the JavaScript string prime to method so that when a user that's using your module is saying module that Prime they're getting the right method and then the method itself we just have to say oh the method is wired to our C++ function Prime in the namespace prime so this looks a little complicated my opinion it's the typical copy and paste kind of situation and then you make sure you why are your function names correctly so yeah you can write native C++ code and use it a node or electron and anybody who's using your module doesn't need to know anything about C++ just pretty nice okay so we compared JavaScript and C++ and obviously can write JavaScript anywhere back and in browser anywhere where you have a JavaScript engine you can write native C++ add-ons when you have node or electron not on the browser where does web assembly fit into this and where does it fit in a performance story I would say very very little about web assembly because we have two more talks today from people who know way more about web assembly so stay for those talks briefly web assembly is a portable binary format for the web portable that means you can put it from machine to machine if you write it at runs on Windows Mac and Linux you don't have to worry about the architecture it's a binary format that means it's binary so it's not for us humans it's for the computer to read and it's for the web that means it runs in the browser so it's a binary format something did we cannot read right away that runs in the browser and it's supported on all platforms multiple browsers that's actually something really really nice all the big browser when this came together and worked together on the specification and they're still working together under specification and they all agreed yes we will implement webassembly according to the specification so you can actually use it because there's no point of having something great and the browser if it's only supported by one browser because then you can't usually you can't use it for any real life work so it's it's really nice that these major companies came together and said we work on this together we're not doing a competition and just one person implements it I think know you can wear assembly is supported in all the major browsers since web assembly is supported by the 88 it's a node you can run webassembly and node and I said it's a binary format so it's not a language that it's not like you learn the web assembly syntax and then write web assembly now you write in your language of choice and then you can compile almost any language to web assemblies so there's more and more compilers coming up the obvious ones are C++ and rest but there's also go and Java and lower compilers and many more all right so what about performance so javascript is still the slowest c++ is the fastest and web assembly is in the middle there just a little bit slower than c++ and that's kind of what you would expect if you know a little bit about web assembly it's not just in time compiler it's statically typed and there's just this tiny little overhead because you have to switch from c plus you're calling web assembly that's why it's not as fast as c++ but it's very close so in summary modern javascript is really really fast it's not like 20 years ago but it's also not like 2011 that was the slide where I showed you 10 seconds versus 80 seconds we are at less than a factor 2 for things that are computationally expensive so unless like there's a very small amount of applications that really need this kind of performance for the computational part so most of the time you're probably just fine sticking with JavaScript you don't have to worry about a different syntax you don't have to worry about build tools your CI like just keep everything in JavaScript it's almost as fast as C++ and it's probably good enough for what you're doing if you earn these rare cases but you really need that performance and yes all apps pretty much all apps should be faster especially if you don't slow Wi-Fi but that's not solved by like changing from JavaScript to C++ like it's probably because you're doing the wrong HTTP calls or you're sending pictures that are too big or your database is too slow but if you do fall into this small amount of applications where your runtime performance is really really important then you can always as long as it's no two-electron and not in the browser you can opt for native add-ons and just write it in C++ and if you do need something really fast in the browser try out web assembly but before you do any of that make sure you measure first and don't optimize blindly thank you [Applause]
Info
Channel: Coding Tech
Views: 50,875
Rating: 4.7389121 out of 5
Keywords: javascript, webassembly, c++, speed, performance
Id: uMuYaES4W3o
Channel Id: undefined
Length: 28min 50sec (1730 seconds)
Published: Mon Jan 28 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.