May 20, 2015 10:00 am

Delivering fast JavaScript performance in Microsoft Edge

In Windows 10 and Microsoft Edge we’ve made a lot of performance advances in the Chakra JavaScript engine. All with the goal of letting your JavaScript code run faster, taking as much advantage of the underlying hardware as possible, so that the web you create and experience in Microsoft Edge provides consistently delightful user experiences across all form factors. Amongst others, one of the key themes for our team during this release has been to closely look at data from customer feedback and telemetry reports, and tune the engine to run fast on the existing web, i.e. the web as it exists today.

A while back, we detailed some of the performance enhancements that we’ve made in the Chakra JavaScript engine in Windows 10 for Microsoft Edge. Improving performance is a never ending theme. In this post we detail some enhancements that we’ve done to Chakra’s JIT compiler, based on feedback and telemetry data from the web as it exists today.

Cross file script inlining

All modern JavaScript compilers support function inlining as a key optimization. Function inlining occurs when the function body of a called function is inserted, or inlined, into the caller’s body as if it was part of the caller’s source, thereby saving the overhead of function invocation and return (register saving and restore). In performance sensitive code paths, inlining could provide up to 20-30% performance boost.

While inlining, all compilers need to play a balancing act. For example, beyond a particular size threshold, the compiler might end up spending more time providing the inlined code’s contextual information to produce optimized JIT code than the time it actually saves by the reduced call overhead. Similarly, when trying to inline functions that are coming from another context or script file, the cost of work that needs to be done (like providing the contextual information) could outweigh the performance benefits that inlining provides.

During the development of Windows 10 and Microsoft Edge, we gathered some data from the existing web to understand how Chakra’s inlining optimization was working on the web. We looked at a randomly selected sample of 3,000 sites from the list of top 10,000 sites and following is what we found:

 Diagram showing inlining on top 3000+ sites  Diagram showing per-site breakdown of cross-script inline restrictions
Chakra inlined only 31% of function calls; 48% of the functions were not getting inlined as the caller and callee functions lived in separate script files Using another view, more than half of the sites in our sample were not inlining 60% or more functions

In Windows 10 and Microsoft Edge, Chakra’s JIT compiler and the execution pipeline has been optimized such that Chakra can now efficiently inline functions that are defined across JavaScript files, without losing the performance benefits achieved via inlining. This optimization enables a lot more JavaScript code on the existing web to get inlined and run faster in Microsoft Edge.

Speeding up global constants using fixed field optimization

ECMAScript6 brought the concept of const values to the JavaScript language. Apart from the language and tooling benefits that constants bring to JavaScript developers, they allow JavaScript compilers to optimize look-up performance. When a property is declared as a const, compilers are sure that the value of that property would not change over the lifetime of the program. And given this guarantee, compilers can optimize to avoid look-up costs for such properties. Look up cost includes the cost to check the type/shape/internal representation of the property, figuring out the actual value stored in the property, and checking whether the value has changed during the course of program execution. For constants, the compilers don’t need to do any of the abovementioned checks.

While the usage of const is catching up on the web, a majority of the existing web does not use the const construct today. Instead, in today’s web, most of the times constants are defined once as a variable on the global object and then used everywhere in the code. In one of the experiments we did with 10,000+ sites on the web, we found that more than 20% of the sites had occurrence of such patterns for defining integer constants, and the average was more than 4 such occurrences per site.

In Windows 10 and Microsoft Edge, we’ve started optimizing Chakra’s parser and the JIT compiler to identify non const variable declarations of integers that are defined globally and are never changed during the course of the execution time of the program. Once identified, the JIT’ed code produced by Chakra is able to substantially reduce the lookup cost associated with such globally defined variables that do not change their shape and value during the execution lifetime of the program, thus extending the performance oriented value proposition of the const statement in ECMAScript 6 to how constants are often used in the web as it exists today.

Improving performance of code within try-catch blocks

Using try-catch is very common on the web as it exists today. However, using try-catch is typically not a recommended best practice, especially when the code path is performance sensitive. Try-catch code is hard to optimize because most operations inside the try block can cause an exception and go to the catch.  This flow is too expensive to model precisely in a JIT compiler.  Different techniques need to be used to model this, which create an additional overhead that needs to be taken care of by the execution machinery.

In a data gathering experiment we did on a sample of top 4500 sites, we noticed that more than 96% of the sites threw JavaScript exceptions caught by script code. In fact, more than 50% of sites threw more than 10 JavaScript exceptions that were handled by script code.

Until Windows 10, Chakra did not optimize code inside of try-catch blocks. In Windows 10 and Microsoft Edge, Chakra’s compiler now has the capability to abstract out the code defined inside of the try-catch blocks and generate optimized JIT code for it. For cases where an exception is not thrown, Chakra now executes such code inside a try block almost on par with regular JIT’ed code (as if the try-catch never existed).

Minified code now brings size and speed benefits

Given the advantages of reducing the size of content to be pulled down to a client (browser), usage of minified JavaScript code is common in today’s web. While investigating a particular performance issue during the Windows10 release, one of the things we found was that some instances of minified code (minified using UglifyJS) were not performing as well as their non-minified equivalent were. In some cases minifiers use code patterns that we thought developers typically don’t use, which is why Chakra had not optimized for the same. So we did a quick experiment to see how common minification is on the web. Out of the top 10,000 sites, we took a random sample of around 4,000 sites and below is what we found:

Minification on Top 4000 sites
 Chart showing 95% of sites had some form of minified code  Chart showing that 77% of these sites had code minified using UglifyJS  Chart showing out 47% of those sites used jQuery minified via UglifyJS
95% of the sites had some form of minified code Out of the 95%, 77% sites had some code that was minified using UglifyJS Out of the 95%, 47% of the sites used jQuery minified via UglifyJS

The experiment confirmed that usage of minified code is extremely popular on the web as it exists and amongst others, UglifyJS is very commonly used in today’s web. So in Windows 10 and Microsoft Edge, we’ve added new fast paths, improved inlining and optimized some heuristics in Chakra’s JIT compiler to ensure that minified code runs as fast, if not faster than the non-minified versions. With these changes, the performance of individual code patterns minified using UglifyJS that we tested, improved between 20-50%.

Array#indexOf optimizations

Usage of arrays is pervasive on the web. Apart from providing polyfills and helper functions, a lot of JavaScript libraries out in the wild try to provide faster implementation of some of the standard Array built-ins that come as a part of the JavaScript language. In an ideal world, all browsers should be fast enough on these built-ins such that libraries could focus more on the polyfill and helper API aspect, rather than having to worry about fixing the performance of built-ins across browser implementations. Stating another way, developers should never feel forced to use a library just to make some of the basic built-ins implemented by all JavaScript engines run faster.

Though we are far away from the ideal world mentioned above, in one of the other of data gathering exercises that we recently did, we tried to measure the most used ECMAScript 5 built-ins on the web as it exists today. The experiment was carried out on a random sample of ~4000 of the top 10,000 sites. The top three hits that we found were: Array#indexOf, Array#map and Array#forEach.

Given the popularity of Array built-ins on the web, in Windows10 and Microsoft Edge, Chakra has optimized how values are retrieved, while the engine traverses a given array. This optimization helps remove the extraneous overhead of visiting the prototype chain and looking up the numeric property corresponding to the index, when holes are encountered in an array. This optimization helps improves the performance of ECMAScript5 Array#indexOf built-in in Chakra and Microsoft Edge by more than 5 times.

So, are we fast yet?

Most of the optimizations that we detailed above are based on data from the web as it exists today, and help the existing web run fast in Microsoft Edge. That said, as much as we’d not like to talk about synthetic benchmarks, we often get asked about how (Chakra in) Microsoft Edge performs on popular JavaScript benchmarks. Here’s a look at the performance enhancements that we’ve delivered so far in Microsoft Edge on two JavaScript benchmarks, vis-à-vis IE11 and other popular browsers.

Chart showing Microsoft Edge leading at Octane 2.0 versus IE11, Chrome Canary, and Firefox Alpha, with a score of 23507 Chart showing Microsoft Edge leading at Jet Stream versus IE11, Chrome Canary, and Firefox Alpha, with a score of 154
All measures collected on 64-bit browsers running 64-bit Windows 10 Technical Preview
System Info: HP Compaq 8100 Elite with Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz (4 cores), 12GB RAM

What do these charts tell? Chakra in Microsoft Edge is way faster than IE11. Looking closely, Chakra has made the following improvements across these benchmarks in Microsoft Edge:

Benchmark Owned By Improvements in Microsoft Edge over IE11
Jet Stream Apple More than 1.6 times faster
Octane 2.0 Google More than 2.25 times faster
Note: In case you are curious why these charts measure the performance of 64-bit browsers instead of 32-bit browsers, the reason is that unlike IE11, Microsoft Edge runs the 64-bit web platform underneath whenever possible. While all modern 64-bit JavaScript engines run a tad bit slower than their 32-bit counterparts, choosing a 64-bit web platform provides several security advantages, some of which are outlined in this blog.

While winning on a benchmark that is not created by us does feel nice, the key is that Microsoft Edge has already come a long way from IE11 in terms of improved JavaScript performance on both, benchmarks and real world web as it exists today.  As mentioned in the beginning, performance is a never-ending pursuit. We will continue pushing the performance boundaries for JavaScript in Microsoft Edge. Please keep your feedback coming, as that helps us improve! You can file a bug at Connect , add feedback on UserVoice, or reach out to us on twitter via @MSEdgeDev.

– Gaurav Seth, Principal PM Lead, Chakra

Updated July 6, 2015 3:58 pm

Join the conversation

  1. Much needed improvements. Really nice.

    > Microsoft Edge runs the 64-bit web platform underneath whenever possible.

    How to tell if it is running 32 or 64? UA?

  2. I haven’t installed Windows 10 on any machine I have yet. How does the 32-bit MS Edge process compare to 32-bit processes of the other browsers in the benchmarks? I’m asking because I assume the lower powered hardware like Raspberry Pi boards and others will be using the 32-bit process.

    • Fairly similar. Also, all optimization mentioned above and in the previous blog about performance are available across architectures.

      — Gaurav Seth [MSFT]

  3. Much improvements with this build, and now I’m using the latest Spartan in the latest build! :)

    However, there is still a question why I get Spartan uses more memory on the specific sites (especially with so many JavaScript or more than one Flash content) while with the same content, Firefox (and even Chrome) use less memory?

    • Thanks for the feedback! While Microsoft Edge is still work in progress, performance issues are often scenario specific and it would be hard to provide a generalized answer. If you are running into major issues, please do share that feedback with repro steps with us at connect (https://connect.microsoft.com/ie/) so that the appropriate owners could take a closer look.
      – Gaurav Seth [MSFT]

  4. I’ve spent years being immersed in Linux et al web development. For years, I’ve seen Microsoft browsers as being second (or third) class citizens. That said, In the past six months that has increasingly changed. I’ve been watching *very* closely the Spartan/Edge progress, and I am *very* impressed. It’s rare that such as large company, such as Microsoft, can shift gears and change so quickly. Microsoft has been changing rapidly and it’s amazing–I applaud you! Please continue this change, it helps little people like myself. I need to be able to develop website that work for ‘everyone’. Not having to worry about single browser intricacies would be a relief! :-) Have fun in Washington state, regards, Gavin

  5. Does this mean there’s also a performance benefit in minifying your JavaScript in Universal Windows Apps?

    • Yes. All changes/optimizations mentioned above are also applicable to Universal Windows Apps.
      – Gaurav Seth [MSFT]

  6. A constant can be optimized, how about an object that frozen? e.g. The keys could be sorted and binary search could be applied in the properties look-up.

    The performance (both in speed and memory usage) of using slice of an array in V8 is not good in my experience. In node.js, it uses a newly created array instead of using [].slice(), especially in tackling function arguments. How about in Chakra?

  7. Hi guys!

    I know this is not the best place for this, but I couldn’t find where developers were supposed to as questions about IE support. I’m trying to figure out what support IE 10+11 have for ContentSecurityPolicy HTTP-header? http://caniuse.com/#feat=contentsecuritypolicy mentions support for sandbox, but I want to hear it from first hand, however https://status.modern.ie/contentsecuritypolicy?term=content%20se doesn’t give any additional information?

    How well is ContentSecurityPolicy supported in current IE versions?

    Thanks!
    Christian

  8. I did some raw Javascript number crunching speed comparisons recently between IE11, FireFox, and Chrome (flops, matrix multiply, random number generation). While Firefox and Chrome were comparable, IE11 was incredibly bad, up to 100 times worse! IE11 gets about 20 Mflops, Chrome and FF about 1,500 MFLOPS. Random number generation (million per second) Chrome 100, FF 25, IE11 3. I also found that IE11 and Chrome couldn’t allocate more than about 1GB of RAM (125 Million Float64) on a 16 GB machine while FF could go to 4GB.
    For number crunching IE11 sucks, blows, stinks, barfs, and flops. Please tell me that Edge is going to be better.
    Here’s the HTML

    Speed of Floating point and Rand()

    WARNING: Allocating more than 1GB of RAM (125 million floating point variables) will fail
    in IE11 and Chrome will crash! FireFox crashes above 4GB.
    IE11 is 10-100 times SLOWER than the other browsers so run it with smaller loop sizes.

    Raw Floating Point Speed MegaFlops. IE11: 20, FFox: 1,500, Chrome: 1,500
    Mandelbrot steps in X and Y:

    Random Number Generation Speed (106 / sec). IE11: 3, FFox: 25, Chrome: 100
    Random Numbers 106:

    Here’s Speed2.js

    “use strict”;

    // Called By: Mandelbrot()
    function iterate(x, y){
    // given the real (= x) and imaginary (=y) parts of a complex number,
    // finds the number of iterations which lie within the circle of
    // radius 2. The Mandelbrot set is those numbers which never leave the
    // circle of radius 2. maxIterations (=200) iterations is supposed to be enough to
    // determine if the number is in the Mandelbrot set.

    var i = 0; // return value
    var rsq, temp, z1 = x, z2 = y;
    var maxIterations = 200;

    for(; i 4.0){break;}
    temp = z1*z1 – z2*z2 + x;
    z2 = 2.0*z1*z2 + y;
    z1 = temp;
    }

    return i;

    } // iterate()

    function Mandelbrot(nX, nY)
    {
    var xMin = -2.0, xMax = 0.5, yMin = -1.0, yMax = 1.0;
    var dX = (xMax – xMin)/ ( nX);
    var dY = (yMax – yMin)/ ( nY);
    var i, j, x, y, num = 0;
    for(i = 0; i < nX; i++){
    x = xMin + i*dX;
    for(j = 0; j < nY; j++){
    y = yMax – j*dY;
    num += iterate(x, y);
    }
    }

    return 10 * num; //each call to iterate() is 10 flops.
    }

    //Time calculating the Mandelbrot set as a test of
    //raw floating point speed, with no memory allocation or
    //array/matrix operations.
    function timeMandelbrot(numStr)
    {
    var num = parseInt(numStr);

    var v, t = secondsFromDate(new Date());
    var numOps = Mandelbrot(num, num);
    t = secondsFromDate(new Date()) – t;

    var speed = numOps / t;
    speed /= 1.0E6;
    alert("Time: " + t +" MegaFLOPS " + speed);
    }

    //Calculates num * 1,000,000 random numbers.
    //The random numbers are summed to stop FireFox optimizer from
    //eliminating the generation loop.
    function timeRand(numStr)
    {
    var num = 1000000 * parseInt(numStr);

    var sum = 0, t = secondsFromDate(new Date());
    for(var i=0; i<num; ++i)
    {
    sum += Math.random();
    }
    t = secondsFromDate(new Date()) – t;

    var ranSpeed = num / t;
    ranSpeed /= 1.0E6;
    alert("Time: " + t +" million random numbers / sec " + ranSpeed);
    }
    //Finds seconds from a Date object.
    function secondsFromDate(dateObj)
    {
    var seconds = dateObj.getSeconds() + 0.001 * dateObj.getMilliseconds();
    seconds += 60.0 * dateObj.getMinutes() + 3600.0*dateObj.getHours();
    return seconds;
    }

    • @Ted. Thanks for the report!
      I ran the test you submitted on a Win10 machine and here are the results I see
      IE11 – Time :16.464999 Million random numbers/sec: 60.7348921
      Chrome Canary 46.0.2456.0 – Time : 8.140999 Million random numbers/sec: 122.8350325
      FF Nightly 2.0a1 (2015-07-15) – Time : 6.084000 Million random numbers/sec: 164.3655489
      MS Edge – Time : 5.883999 Million random numbers/sec: 169.9524133
      Note: I had to make a couple of edits to the this line of code “for(; i 4.0){break;}” in your iterator function, which seemed to have typos.

      In short, yes, there are a lot of improvements in Microsoft Edge in this area.

      • Yes the cut and paste left out some lines, sorry about that. I am surprised that IE11 does so well in your tests or maybe I am surprised it did so badly in mine. I was running Win 8.1 FYI. The speed difference may be due different random number generators, rather than raw floating point speed. Did you try raw floating point tests like the Mandelbrot generator, or multiplying two matrices (which includes array look-up)? Because I found huge differences in those tests, which I don’t understand.

  9. How do I set up my JavaScript to target the AJAX feed to scan, and target the feed for presentation requirements?

  10. Can you clarify the wording around the const optimisations? A const value can change which would change it’s shape? I can see how the wording makes sense for primitives but apart from that, surely only the type can be guaranteed?

    • In this context, think of shape as the internal representation of an object and the value it contains. For global vars that are integers, Chakra is able to substantially reduce the lookup cost associated with globally defined variables, as long as it’s value or type is not changed.

  11. HI,
    In Microsoft edge. I disabled JS by using following steps:
    “Computer Configuration”: > “Administrative Templates” > “Windows Components” > “Microsoft Edge”.

    but the code inside script tag still runs. why? ideally it should not like other browser. What is the reason ?noscript tag also does not support by ms edge.