Friday, November 20, 2015

Impact of Reflection on Annotations in Dart


I had a good time messing about with code annotations last night. Ostensibly I am still working on the Flyweight Pattern in Dart for the forthcoming Design Patterns in Dart. Tonight, however, I am really just exploring annotations a little further.

I am somewhat surprised that the DartPad for last night's code (https://dartpad.dartlang.org/c7dabc0c57a93e8d88d7) compiled. I am a little fuzzy on the details of DartPad, but there has to be some JavaScript compilation taking place to make it work. The thing is, when I compile last night's code into JavaScript, it is huge.

The code itself is a little over 4k of Dart:
$ ls -lh bin/coffee_orders.dart lib/coffee_shop.dart 
-rwxr-xr-x 1 chris chris 1.6K Nov 21 00:12 bin/coffee_orders.dart
-rw-r--r-- 1 chris chris 2.8K Nov 21 00:18 lib/coffee_shop.dart
But when I compile it:
$ dart2js bin/coffee_orders.dart -o bin/tmp.js                                                                 
bin/coffee_orders.dart:
Warning: 
****************************************************************
* WARNING: dart:mirrors support in dart2js is experimental,
*          and not recommended.
*          This implementation of mirrors is incomplete,
*          and often greatly increases the size of the generated
*          JavaScript code.
*
* Your app imports dart:mirrors via:
*   coffee_orders.dart => package:flyweight_code => dart:mirrors
*
* You can disable this message by using the --enable-experimental-mirrors
* command-line flag.
*
* To learn what to do next, please visit:
*    http://dartlang.org/dart2js-reflection
****************************************************************


Hint: 2 hint(s) suppressed in package:flyweight_code.
Hint: When run on the command-line, the compiled output might require a preamble file located in:
  /lib/_internal/js_runtime/lib/preambles.
bin/coffee_orders.dart:
Hint: 2367 methods retained for use by dart:mirrors out of 3519 total methods (67%).

bin/packages/flyweight_code/coffee_shop.dart:4:1:
Info: This import is not annotated with @MirrorsUsed, which may lead to unnecessarily large generated code.
Try adding '@MirrorsUsed(...)' as described at https://goo.gl/Akrrog.
import 'dart:mirrors';
^^^^^^^^^^^^^^^^^^^^^^
I get nearly 4k of console warnings.

I kid, I kid. What is not funny however, is that, as the warning suggests, I get very large JavaScript output:
ls -lh bin/tmp.js
-rw-r--r-- 1 chris chris 1.7M Nov 21 00:25 bin/tmp.js
1.7Mb is insane.

The code works (if I follow the warning about preambles):
$ cat ~/local/dart/dart-sdk/lib/_internal/js_runtime/lib/preambles/d8.js bin/tmp.js > bin/coffee_orders.dart.js
$ node bin/coffee_orders.dart.js
Served Cappuccino to Fred.
Served Espresso to Bob.
Served Frappe to Alice.
Served Frappe to Elsa.
Served Coffee to null.
Served Coffee to Chris.
Served Mochachino to Joy.
-----
Served 7 coffee drinks.
Served 5 kinds of coffee drinks.
For a profit of: $27.2
But 1.7Mb is not going to fly.

I annotated my concrete flyweight classes with the Flavor constant:
// Annotation Class
class Flavor { const Flavor(); }
// Annotation instance
const flavor = const Flavor();

@flavor
class Cappuccino implements CoffeeFlavor {
  String get name => 'Cappuccino';
  double get profitPerOunce => 0.35;
}
// Other concrete instances...
So, I ought to be able to slim the resultant JavaScript down simply by telling dart2js that it only has to worry about mirrors for Flavor. This is what the @MirrorsUsed annotation does. In my case, I am using mirrors as "meta targets" -- reading annotation / metadata from the code itself. I add the corresponding @MirrorsUsed() annotation before the import of dart:mirrors:
library coffee_shop;

@MirrorsUsed(metaTargets: "coffee_shop.Flavor")
import 'dart:mirrors';
// ...
Now, when I compile my code, dart2js only retain 38 methods for use with mirrors instead of 2367:
$ dart2js bin/coffee_orders.dart -o bin/tmp.js --enable-experimental-mirrors
bin/coffee_orders.dart:
Hint: 38 methods retained for use by dart:mirrors out of 629 total methods (6%).

bin/packages/flyweight_code/coffee_shop.dart:4:1:
Info: Import of 'dart:mirrors'.
import 'dart:mirrors';
^^^^^^^^^^^^^^^^^^^^^^
That is bound to lead to some smaller compiled code. And indeed, it comes in nearly 90% smaller than before:
$ cat ~/local/dart/dart-sdk/lib/_internal/js_runtime/lib/preambles/d8.js bin/tmp.js > bin/coffee_orders.dart.js
$ ls -lh bin/tmp.js bin/coffee_orders.dart.js                                                                  
-rw-r--r-- 1 chris chris 294K Nov 21 00:36 bin/coffee_orders.dart.js
-rw-r--r-- 1 chris chris 284K Nov 21 00:35 bin/tmp.js
That seems a fine stopping point for tonight. I will continue this exploration tomorrow with reflectable, a potential replacement for dart:mirrors.


Day #9

3 comments:

  1. Yes, DartPad uses dart2js to compile your Dart into JavaScript so that it can run in your browser.

    ReplyDelete
  2. I am also looking at reflectable, and am going you can save me some research with your next post.

    ReplyDelete
  3. I am also looking at reflectable, and am going you can save me some research with your next post.

    ReplyDelete