Uploaded image for project: 'Titanium SDK/CLI'
  1. Titanium SDK/CLI
  2. TIMOB-12551

JavaScript-to-JavaScript optimizing compiler

    Details

      Description

      The performance of native bridge invocations shall be improved in Q1
      for both iOS and Android. This will be done by creating an optimizing
      javascript-to-javascript compiler that identifies non-performant
      Titanium API use-cases and transforms them into performant native
      bridge invocations.

      The plan for completing this task is:

      1. Automate collection of app performance data.

      2. Identify Titanium performance bottlenecks from performance data.

      3. Analyze javascript AST for common Titanium API idioms.

      4. Create optimization algorithms (AST transformations) for these
      idioms identified as performance bottlenecks.

      From Jeff's README.md in his compiler prototype project:

      Compiler Prototype
      ==================

      The compiler prototype is a simple set of compile-time optimizations
      that are applied to speed up native bridge invocations.

      Concept
      -------

      The concept is very simple.

      The Javascript native bridge binds a set of JavaScript objects (the
      Titanium API) and eventually a method is bound into a callback
      implementation in native land. The JS engine performs native bridge
      calls to lookup the JavaScript object and eventually invoke the bound
      native function in this simple example:

      > Ti.API.log('foo')

      The JS engine makes the following invocations underneath the cover:

      > Ti => Object
      > Object.API => Object
      > Object.log => Function
      > Function('foo')

      So, a simple one-liner requires 4 invocations in the JS engine.

      Additionally, in this example, foo is an argument that must be
      marshalled from a JS native object into a native object. In
      Objective-C, this turns a JSValueRef (or JSStringRef) into a
      NSString. This happens before the underlying JSFunction is mapped into
      an Objective-C method. To make matters worse, in Objective-C, we have
      to do additional lookups to find the appropriate TiModule and then
      build an NSInvocation to invoke the method against the Objective-C
      class. All of this takes time.

      This prototype attempts to speed up the invocation above by performing
      a few simple steps.

      First, we avoid the Object lookups by turns a Titanium API into a
      bound symbol in the JS engine. So, Ti.API.log become T$1 (as an
      example).

      Second, we attempt to speed up static arguments that are required to
      be marshalled during invocation into a symbol table which are only
      marshalled once. The symbol table is pre-compiled and subsequent
      references of the same symbol will use a pre-marshalled copy.

      Third, we pre-bind a compiled method and set of pre-marshalled
      arguments into a jump table. This is similar to using a function
      pointer (but as an instance of a compiled pointer to a module and
      invocation).

      The result is that you can get 50-75% pure invocation speed ups. For
      methods that have static arguments, you can achieve even better
      results.

      Limitations
      -----------

      The current prototype uses a titanium module and requires a compile
      method to be called at the top of each compiled file to load up the
      symbols and bind the symbols. This has convenience in that we can do
      it without having to make any changes to Titanium itself. However, we
      may want to consider adding the compile method into Titanium
      (unpublished) to make maintenance easier and eliminate the need for a
      module.

      The second limitation is that we are speeding up only function calls
      and a few global symbols (such as Ti.UI.FILL). Eventually, we may
      want to do the same to properties.

      Third, the prototype is using a compiler hook (good) but only is
      working with Alloy. This is not a limitation per se, but i didn't
      want to have to deal with figuring out how to redirect the compiled JS
      files into a separate temporary directory and causing XCode to point
      at that instead of Resources – since we don't want to override
      Resources (except in Alloy, that's how it operates, so that's OK).

      Fourth, I'm using a uglify-based compiler by hand instead of using
      Bryan's titanium code processor. The current implementation of the
      code processor doesn't allow you to mutate code in his plugins (or in
      the processor itself). Ideally, this would actually be a code
      processor plugin that would simply mutate code in a pipeline during
      compilation.

      Fifth, the current prototype is iOS only. This is because I'm most
      familiar with iOS and don't know the Android compiler / bridge for V8
      as well as the old version. However, I think this same concept works
      for Android conceptually.

      Six, since I'm not using the code processor, I'm not using the JSCA
      file to understand the API. Moving to the code processor eliminates
      that issue.

      The last overall limitation is that we might want to actually
      pre-compile out the jump tables and use actually compiled function
      callbacks instead of the TiCompiledMethod object. If we moved to more
      of a true compiled / generated code, that would be rather trivial.
      This would likely give us even more speedup and much less object
      garbage and much lower memory. You would simply generate the C
      function that would directly call the module and method instead of
      having to have a generic TiCompiledMethod and you could by pass the
      entire Kroll overhead. I believe the new V8 compiled bindings do
      something similar in Android.

      Components
      ----------

        1. CLI Hook

      The CLI hook file is under plugins/ti.compiler/hooks and in the file
      ti.compiler.js

        1. Module Source

      The module source for iOS is under compiler_module.

      Notes


      The ti.compiler plugin must come after the ti.alloy in tiapp.xml.

      Afterthought
      ------------

      I think this concept can be dramatically further expanded to really
      build a compiler that can pre-process code and optimize, compile and
      generally make performance a lot faster than it does today.

        1. Collapsing properties

      For example, take the following code:

      var v = Ti.UI.createView();
      v.width = Ti.UI.FILL;
      v.height = Ti.UI.FILL;
      v.backgroundColor = "red";
      w.add(v);

      This could easily be re-written to be much faster (prior to
      optimizations from prototype):

      w.add(Ti.UI.createView(

      {width:Ti.UI.FILL,height:Ti.UI.FILL,backgroundColor:"red"}

      ));

      After optimizations further:

      w.add(T$1());

        1. Multiple invocations

      Another example that is common, adding multiple objects to a view
      heirarchy.

      w.add(view1);
      w.add(view2);
      w.add(view3);

      This could be optimized into:

      w.add(view1,view2,view3);

      In this example, passing 3 objects over a view to add to the window
      will be MUCH faster because you go from 3 separate invocations to one
      and more importantly, because this method is required to be invocked
      on the UI Thread, 3 separate UI thread blocks.

      Additionally, at some point, you could move the same optimized method
      onto the native side such as it would turn into the following:

      T$1();

      And on the native side, it would simply do the work of adding the 3
      views to a window by native references.

        1. Subsequent code blocks

      My ultimate belief is that most of the static code written in Titanium
      JS can be pre-compiled and essentially reduced into few JS engine
      functions.

      Take the following static code block:

      var w = Ti.UI.createWindow();
      var v = Ti.UI.createView(

      {width:TI.UI.FILL,height:TI.UI.FILL,backgroundColor:"white"}

      );
      var b = Ti.UI.createButton(

      { text:"Hello", width:TI.UI.SIZE, height:Ti.UI.SIZE }

      );
      v.add(b);
      w.add(v);
      b.addEventListener(function()

      { alert("hello world"); });
      w.open();

      This should be able to be reduced in JS code to:

      var r$1 = T$1(), w = r$1[0], v = r$1[1], b = r$1[2];
      b.addEventListener(function(){ alert("hello world"); }

      );
      w.open();

      We should be able to take subsequent code blocks that are static and
      collapse them into code blocks in native that can be mapped to one JS
      symbol pointer.

        1. String consts

      We might be able to get much better optimizations in the JS engine by
      turning static code strings into JS consts at compile time. In a ton
      of cases in an app, you will use inline strings. Its generally faster
      to declare them as a const and then use the const variable name
      instead. This could be a simple optimizations done by the compiler.

        1. Dead code removal

      We need to remove dead code or unused variables and functions. For
      example, in Alloy in the alloy.js file, we define a function named
      isTabletFallback. This method isn't used. We could simply optimize
      on compile and deterine which functions / symbols are reachable and
      then remove them if not.

      For example, Alloy generates the following:

      var Alloy = require("alloy"), _ = Alloy._, Backbone = Alloy.Backbone;

      Alloy.createController("index");

      This is useful is you need to reference Backbone or underscore
      libraries in the app.js stub. However, in almost all scenarios, the
      developer never does this in a standard Alloy app.

      The code above could be easily reduced to the following if it's not
      modified:

      require('alloy').createController('index')

      This creates no variables in memory that have to be later garbage
      collected and is much more efficient in execution.

      • Jeff

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                cbarber Chris Barber
                Reporter:
                mlangston Matt Langston
              • Watchers:
                1 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:

                  Backbone Issue Sync

                  • Titanium SDK/CLI <> Titanium Mobile
                    Synced with:
                    TIMOB-15523
                    Sync status:
                    ERROR
                    Last received:
                    Last sent:

                    Git Source Code