Skip to main content

Why dart2js produces faster JavaScript code from Dart

The dart2js compiler, which converts Dart to JavaScript, now produces smaller and faster code thanks to its global type inferencer. By analyzing the entire program, the compiler can eliminate bailouts and redundant checks, resulting in code that runs faster on modern JavaScript engines.

As evidenced by the graph below, the performance of the code generated by dart2js (the purple line) now slightly outperforms the original hand-written benchmark code (the gold line). The Dart VM, which natively runs Dart code, is the top line. High is better in this benchmark (Dart code, JavaScript code).

Taken on 2012/03/28. From dartlang.org/performance

Type inferencing helps performance

Nicolas Geoffray, engineer on dart2js, has been working on the beginnings of the global type inferencer. In a recent interview (video) he showed examples of the original Dart code, the previously generated JavaScript code, and the new more optimized JavaScript code.





Code gets smaller, faster

For reference, here is a snippet of raw Dart code from the original benchmark:


 void addConstraintsConsumingTo(Variable v, List<Constraint> coll) {  
  Constraint determining = v.determinedBy;  
   for (int i = 0; i < v.constraints.length; i++) {  
    Constraint c = v.constraints[i];  
    if (c != determining && c.isSatisfied()) {  
     coll.add(c);  
    }  
   }  
 }  

Previously, the dart2js compiler would output this JavaScript code (for why, see Preserving Semantics below):


 addConstraintsConsumingTo$2: function(v, coll) {  
   var determining, i, t1, c;  
   determining = v.get$determinedBy();  
   i = 0;  
   while (true) {  
    t1 = $.get$length$as(v.get$constraints());  
    if (typeof t1 !== "number")  
         return bailout(1, v, coll, i, determining, t1);  
    if (!(i < t1))  
     break;  
    t1 = v.get$constraints();  
    if (t1.constructor !== Array)  
     return bailout(2, v, coll, i, determining, t1);  
    if (i >= t1.length)  
     throw $.ioore(i);  
    c = t1[i];  
    if (!$.$eq(c, determining) && c.isSatisfied$0() === true)  
     coll.push(c);  
    ++i; 


Since revision 20130 of dart2js, the JavaScript output now looks like this:

  addConstraintsConsumingTo$2: function(v, coll) {  
   var determining, t1, i, c;  
   determining = v.determinedBy;  
   for (t1 = v.constraints, i = 0; i < t1.length; ++i) {  
    c = t1[i];  
    if ((c == null ? determining != null : c !== determining)  
      && c.isSatisfied$0()) {  
     coll.push(c);  
    }  
   }   

The second version is obviously smaller, and runs faster in part because:

  • bailout code is removed
    • ex: no more return bailout(1...
  • type checks are removed
    • ex: no more typeof t1 !== "number"
  • the while loop is replaced by a for loop
  • function calls are replaced by field access
    • ex: v.get$determinedBy() is now v.determinedBy


Preserving null semantics

Remember that Dart is not just a different syntax for JavaScript, but is itself a new language and libraries with their own semantics. These semantics need to be preserved when compiled to JavaScript, which helps to explain why the original JavaScript code was so verbose. However, the global type inferencing can now help to reduce the verbosity, without changing the semantics of the operations.

Treatment of nulls is a good example of differing behavior between Dart and JavaScript. Consider this code snippet:


 var x = null;  
 var y = 1;  
   
 var results = x + y;  
   
  // Dart throws NoSuchMethodError because the null object  
  // doesn't define the + operator.  
  // However, JavaScript sets results to 1.


Many developers appreciate being told when they try to use a null variable, and thus like the NoSuchMethodError. The dart2js compiler needs to ensure this behavior is retained. However, if dart2js can determine during program compilation that x is never null, then dart2js can eliminate the extra checks and just emit the straightforward JavaScript code.

[Disclaimer! What follows is a snapshot of dart2js's behavior on 2013/03/28.]

Here is an example. Consider the following code:

 var x = null;  
 var y = 1;  
   
 var results = x + y;
 print(results);

Notice how x is null, which forces dart2js to emit this code:


 $.main = function() {  
  $.Primitives_printString($.toString$0($.JSNull_methods.$add(null, 1)));  
 };


However, if x is not null, dart2js can generate intelligent code that eliminates the null handling:


 $.main = function() {  
  $.Primitives_printString($.JSInt_methods.toString$0(3));  
 };  


Notice how dart2js inlines the addition into the literal value 3.

Here is a more complex example. Pay close attention to the complete lack of type annotations. This shows that dart2js performs its type inferencing based on how objects are used, not on how variables are annotated.  (Remember that Dart is an optionally typed language, so the type annotations are ignored at runtime. However, as you can see, the runtimes still make intelligent decisions.)



 add(x , y) {  
  var z = x * 2;  
  return z + y;  
 }  
   
 main() {  
  var x = null;  
  var y = 1;  
   
  var result = add(x, y);  
   
  print(result);  
 }  


Because x is null, dart2js outputs the following JavaScript code:


 $.main = function() {  
  $.Primitives_printString($.toString$0($.$add$ns($.JSNull_methods.$mul(null, 2), 1)));  
 };  
   
 $.$add$ns = function(receiver, a0) {  
  if (typeof receiver == "number" && typeof a0 == "number")  
   return receiver + a0;  
  return $.getInterceptor$ns(receiver).$add(receiver, a0);  
 };  


Now consider this code, where x is not null:


 add(x , y) {  
  var z = x * 2;  
  return z + y;  
 }  
   
 main() {  
  var x = 2;  
  var y = 1;  
   
  var result = add(x, y);  
   
  print(result);  
 }  


The dart2js compiler knows that both x and y are not null, and generates this code:


 $.main = function() {  
  $.Primitives_printString($.JSInt_methods.toString$0(5));  
 };  


Pretty impressive, if I do say so! Notice how dart2js inlines the call to add, and all the math, down to the literal number 5.

One more example of how dart2js can look at nulls. Consider this code, which explicitly checks for null parameters and throws an exception:


 add(x , y) {  
  if (x == null || y == null) throw new ArgumentError('must not be null');  
  var z = x * 2;  
  return z + y;  
 }  
   
 main() {  
  var x = null;  
  var y = 1;  
   
  var result = add(x, y);  
   
  print(result);  
 }  


In a sense, you're putting in your own message to dart2js: "assume that if variables get past this check, that they are not null." Therefore, dart2js can generate the following code:


 $.add = function(x, y) {  
  if (x == null || false)  
   throw $.$$throw($.ArgumentError$("must not be null"));  
  return $.$add$ns($.JSNull_methods.$mul(x, 2), y);  
 };  
   
 $.main = function() {  
  $.Primitives_printString($.toString$0($.add(null, 1)));  
 };  


Not as good as when both x and y are not null, but better than the the code generated without the null guards.

The main point here is that dart2js maintains Dart semantics whenever it compiles to JavaScript. With type inferencing, it's now generating smarter, smaller, and ultimately faster code without sacrificing semantics.

Preserving index out of bounds semantics

Another example of different behavior between Dart and JavaScript is what happens when you access an index that is out of bounds of an array or list. Consider this code:


 var fruits = ['apples', 'oranges'];  
    
 var fruit = fruits[99];  
   
  // Dart will throw a RangeError because fruits only has two elements.  
  // JavaScript will set fruit to undefined.  

Again, many developers appreciate being explicitly told when they try to access an index that is out of bounds. To bring this behavior to JavaScript, dart2js needs to insert code and logic. For example:


 $.main = function() {  
  var fruits = ["apples", "oranges"];  
  if (99 >= fruits.length)  
   throw $.ioore(99);  
  $.Primitives_printString($.toString$0(fruits[99]));  
 };  


However, if dart2js can determine that an index access is never out of bounds of the list, it can optimize the generated code. There are two ways to make a list whose length is known at compile time: constant lists and fixed-length lists.

Here is an example of using a constant list:


 main() {  
  var fruits = const ['apples', 'oranges'];  
  var fruit = fruits[99];  
   
  print(fruit);  
 }  


The dart2js compiler will output the following code, because it knows the length of the list at compile time:


$.main = function() {  
  throw $.ioore(99);  
  $.Primitives_printString($.toString$0($.List_apples_oranges[99]));  
 };  
 // ...  
 $.List_apples_oranges = Isolate.makeConstantList(["apples", "oranges"]);  


Notice how the exception is immediately thrown, because the compiler knows at compile time that 99 is out of the range of the list.

Summary

The dart2js compiler, which converts Dart code to JavaScript, must maintain Dart semantics in the generated output. Due to recent updates, dart2js can perform whole program analysis and in some cases eliminate bailouts, typechecks, range checks, and other code while keeping consistent with the semantics of the Dart language and libraries. This means smaller, and often faster, code.

The team isn't done yet, there's lots more work to be done. We caution developers from writing code specifically tuned for dart2js's behavior today, as there will be plenty of changes to the code generation algorithms. If you have some code that you believe should be generated more efficiently, please open a bug at dartbug.com/new so we can fix it for you.

Comments

  1. That is very impressive, and I will definitely consider Dart for my next project. But one thing left me wonder, why don't you compare the performance to Java/JVM? I mean you have developed a language that is designed to be run in a virtual machine, why not aim high and try to beat JVM kind of performance?
    I know it's going to be a long ride since the JVM is a pretty huge headstart when it comes to development times, but I am sure you can get quite close to it :)

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Sorry, reposting with link corrected and a couple small text changes.

    The focus in this article is precisely on JS performance, for running in browsers without a Dart VM.

    Currently, concerning custom VMs, for some benchmarks, V8 lags a bit behind Java, and Dart VM lags behind V8, although Google clearly shows the Dart VM beating V8 in others (see the chart referenced at the top of this article). If Google does achieve the goal of making the Dart VM faster than V8 overall (some goals stated in the past are "at least twice as fast"), then it will be competitive with Java. But they clearly aren't really there yet. Maybe in some areas, but not overall yet, I don't think. My own number-crunching quick tests also show it some 4x slower than V8.

    ReplyDelete
    Replies
    1. Hardly any Dart programs have been contributed to the benchmarks game -- maybe they just aren't the best programs. Better Dart programs would be welcome.

      Delete
  4. Impressive guys.

    What would be cool would be to have dart2js to generate optimized OdinMonkey javascript(Firefox 22). This would be really cool for 2 reasons.

    1. Send a message to the world that this isn't just a chrome technology.
    2. Enscripten toolchain is kind of a pain to do.

    ReplyDelete
    Replies
    1. What is optimized OdinMonkey javascript? Do you mean asm.js? asm.js is a compile target for non-GC languages like C. Compiling Dart to asm.js doesn't make much sense, because Dart is a GC language and runtime.

      Delete
  5. I really want to do my Apps Script in Dart instead of JS. Any roadmap for that?

    ReplyDelete
  6. "faster Javascript code"... faster than what, exactly? Faster than itself? Is Dart2JS Javascript code anywhere near as fast as hand-written Javascript code, or code output by the Typescript compiler, or Coffeescript code compiled to Javascript, or Livescript, or... any comparison whatsoever with something that isn't directly produced by Dart?

    I love the idea of programming Javascript with a proper language, but no developer in his right mind would use Dart right now when the Javascript is that bloated and slow. I just compiled a 17-line Dart file with dart2js, used the --minify option, and the resulting file is 84k. That's ridiculous.

    For reference, the source dart file is this:

    import 'dart:html';

    InputElement toDoInput;
    UListElement toDoList;

    void main() {
    toDoInput = query('#to-do-input');
    toDoList = query('#to-do-list');
    toDoInput.onChange.listen(addToDoItem);
    }

    void addToDoItem(Event e) {
    var newToDo = new LIElement();
    newToDo.text = toDoInput.value;
    toDoInput.value = '';
    toDoList.children.add(newToDo);
    }

    which comes directly from the Dart tutorials.

    ReplyDelete
    Replies
    1. Why would that be ridiculous? Same example with minified jQuery would be over 90k. And nobody seems to complain.

      Delete
  7. I love the idea of programming Javascript.

    ReplyDelete
  8. I also have to question the validity of the benchmarks.

    The benchmarks themselves are pretty tangent to the web since they only measure raw computation time and not actual issues relevant to the web such as "time to get everything done on the page" or "time the user can actually do anything on a average application," both of which are irrelevant to how fast all your scripts put together execute, and heavily related to how you package dart and send it to the browser. If I have 10 classes how long does it take? If I have 100 classes how long does it take? If I have 1000 classes how long does it take? And does that scale up in a sane manner.

    Outputting the page to a first time user seems to have a cost of 1s to 3s (yes, 3 seconds) and the way dart works, if you make a small application of 10-20 classes you'll see 10-20+ files loaded (this is the VM version) which by the word of your own engineers (https://www.youtube.com/watch?v=Il4swGfTOSM) should be considered abysmal. For the js version, the fact that any import-anything results in a full compilation of said library (typically +4000 lines, or 80k gziped) slowly adds up to what I'm seeing as monster javascript files for what is essentially no real code.

    Though the real bummer here is that all your dart files are no different then separate javascript files.

    I understand you want to be all lovelydovely with java developers (even though I hardly ever see any java-based sites), but unless you rethink your distribution system, and rethink your dart2js to actually produce code that's not designed only for people running on desktops with 100 MB/s connections (which don't need your fast script execution to begin with), dart is slowly and surely becoming the language for creating slow and resource heavy web apps. It won't matter if your code is 2 times faster or even 10 times faster, if you produce 50 times the necessary code! (files, instructions, etc)

    ReplyDelete

Post a Comment

Popular posts from this blog

Const, Static, Final, Oh my!

Posted by Seth Ladd

(This is an "oldie but a goodie" misc@dartlang.org post originally written by Bob Nystrom. It is being posted here as the explanations still ring true.)

Bob writes:


"static", "final", and "const" mean entirely distinct things in Dart:

"static" means a member is available on the class itself instead of on instances of the class. That's all it means, and it isn't used for anything else. static modifies *members*.

"final" means single-assignment: a final variable or field *must* have an initializer. Once assigned a value, a final variable's value cannot be changed. final modifies *variables*.

"const" has a meaning that's a bit more complex and subtle in Dart. const modifies *values*. You can use it when creating collections, like const [1, 2, 3], and when constructing objects (instead of new) like const Point(2, 3). Here, const means that the object's entire deep state can be determ…

AngularDart 4

AngularDart v4 is now available. We've been busy since the release angular2 v3.1.0 in May. Not only did we "drop the 2", but we also improved the compiler and tightened up the framework to give you smaller code, we updated the package structure to improve usability, and we added several new features. Check out the updated documentation to get started.
Just angular Upgrading to v4 will require more than updating your version constraint. The package has changed names (back) to angular – dropping the 2. You'll need to update your pubspec.yaml and the corresponding imports in your code. In most instances, find-and-replace should do the trick. Going forward, the package will be called package:angular. We'll just update the version number.
Smaller code The updated compiler in 4.0 allows type-based optimizations that not only improve runtime performance but generate better code because we are able to strongly type templates. A big result of the update is that many ap…

The new AdWords UI uses Dart — we asked why

Google just announced a re-designed AdWords experience. In case you’re not familiar with AdWords: businesses use it to advertise on google.com and partner websites. Advertising makes up majority of Google’s revenue, so when Google decides to completely redo the customer-facing front end to it, it’s a big deal. The Dart team is proud to say that this new front end is built with Dart and Angular 2. Whenever you asked us whether Google is ‘even using Dart for anything,’ this is what we had in mind but couldn’t say aloud. Until now. We asked Joshy Joseph, the primary technical lead on the project, some questions. Joshy is focusing on things like infrastructure, application latency and development velocity, so he’s the right person to ask about Dart.Q: What exactly did we launch on Monday?It’s a complete redesign of the AdWords customer experience that is rolling out slowly as a test to a small initial set of advertisers. The most noticeable thing is probably the Material Design look and f…