{ "id": "109040", "key": "TIMOB-12551", "fields": { "issuetype": { "id": "7", "description": "gh.issue.story.desc", "name": "Story", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [], "resolution": { "id": "2", "description": "The problem described is an issue which will never be fixed.", "name": "Won't Fix" }, "resolutiondate": "2017-07-19T17:34:48.000+0000", "created": "2013-02-01T18:47:21.000+0000", "priority": { "name": "Low", "id": "4" }, "labels": [ "andoid", "ast_transformation", "core", "ios", "javascript", "javascriptcore", "performance", "source_maps" ], "versions": [], "issuelinks": [ { "id": "25828", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "105164", "key": "TIMOB-11812", "fields": { "summary": "iOS: Optimize bridge-crossing instances in Titanium Mobile API", "status": { "description": "The issue is open and ready for the assignee to start work on it.", "name": "Open", "id": "1", "statusCategory": { "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "priority": { "name": "Low", "id": "4" }, "issuetype": { "id": "6", "description": "gh.issue.epic.desc", "name": "Epic", "subtask": false } } } }, { "id": "28213", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "113502", "key": "TIMOB-13703", "fields": { "summary": "Android: Experiment: An optimizing compiler will improve app performance", "status": { "description": "The issue is open and ready for the assignee to start work on it.", "name": "Open", "id": "1", "statusCategory": { "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "priority": { "name": "Low", "id": "4" }, "issuetype": { "id": "7", "description": "gh.issue.story.desc", "name": "Story", "subtask": false } } } } ], "assignee": { "name": "cbarber", "key": "cbarber", "displayName": "Chris Barber", "active": true, "timeZone": "America/Chicago" }, "updated": "2018-08-02T22:20:03.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" }, { "id": "13103", "name": "CLI", "description": "Node-based command line interface" }, { "id": "13102", "name": "Code Processor", "description": "Analyzes JavaScript source" }, { "id": "12305", "name": "Core", "description": "Central component of the Platform (outside of Tooling, or platform specific implementation details)" }, { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "description": "The performance of native bridge invocations shall be improved in Q1\r\nfor both iOS and Android. This will be done by creating an optimizing\r\njavascript-to-javascript compiler that identifies non-performant\r\nTitanium API use-cases and transforms them into performant native\r\nbridge invocations.\r\n\r\nThe plan for completing this task is:\r\n\r\n1. Automate collection of app performance data.\r\n\r\n2. Identify Titanium performance bottlenecks from performance data.\r\n\r\n3. Analyze javascript AST for common Titanium API idioms.\r\n\r\n4. Create optimization algorithms (AST transformations) for these\r\n idioms identified as performance bottlenecks.\r\n\r\n\r\nFrom Jeff's README.md in his compiler prototype project:\r\n\r\nCompiler Prototype\r\n==================\r\n\r\nThe compiler prototype is a simple set of compile-time optimizations\r\nthat are applied to speed up native bridge invocations.\r\n\r\nConcept\r\n-------\r\n\r\nThe concept is very simple. \r\n\r\nThe Javascript native bridge binds a set of JavaScript objects (the\r\nTitanium API) and eventually a method is bound into a callback\r\nimplementation in native land. The JS engine performs native bridge\r\ncalls to lookup the JavaScript object and eventually invoke the bound\r\nnative function in this simple example:\r\n\r\n\t> Ti.API.log('foo')\r\n\r\nThe JS engine makes the following invocations underneath the cover:\r\n\r\n\t> Ti => Object\r\n\t> Object.API => Object\r\n\t> Object.log => Function\r\n\t> Function('foo')\r\n\r\nSo, a simple one-liner requires 4 invocations in the JS engine.\r\n\r\nAdditionally, in this example, foo is an argument that must be\r\nmarshalled from a JS native object into a native object. In\r\nObjective-C, this turns a JSValueRef (or JSStringRef) into a\r\nNSString. This happens before the underlying JSFunction is mapped into\r\nan Objective-C method. To make matters worse, in Objective-C, we have\r\nto do additional lookups to find the appropriate TiModule and then\r\nbuild an NSInvocation to invoke the method against the Objective-C\r\nclass. All of this takes time.\r\n\r\nThis prototype attempts to speed up the invocation above by performing\r\na few simple steps.\r\n\r\nFirst, we avoid the Object lookups by turns a Titanium API into a\r\nbound symbol in the JS engine. So, Ti.API.log become T$1 (as an\r\nexample).\r\n\r\nSecond, we attempt to speed up static arguments that are required to\r\nbe marshalled during invocation into a symbol table which are only\r\nmarshalled once. The symbol table is pre-compiled and subsequent\r\nreferences of the same symbol will use a pre-marshalled copy.\r\n\r\nThird, we pre-bind a compiled method and set of pre-marshalled\r\narguments into a jump table. This is similar to using a function\r\npointer (but as an instance of a compiled pointer to a module and\r\ninvocation).\r\n\r\nThe result is that you can get 50-75% pure invocation speed ups. For\r\nmethods that have static arguments, you can achieve even better\r\nresults.\r\n\r\nLimitations\r\n-----------\r\n\r\nThe current prototype uses a titanium module and requires a compile\r\nmethod to be called at the top of each compiled file to load up the\r\nsymbols and bind the symbols. This has convenience in that we can do\r\nit without having to make any changes to Titanium itself. However, we\r\nmay want to consider adding the compile method into Titanium\r\n(unpublished) to make maintenance easier and eliminate the need for a\r\nmodule.\r\n\r\nThe second limitation is that we are speeding up only function calls\r\nand a few global symbols (such as Ti.UI.FILL). Eventually, we may\r\nwant to do the same to properties.\r\n\r\nThird, the prototype is using a compiler hook (good) but only is\r\nworking with Alloy. This is not a limitation per se, but i didn't\r\nwant to have to deal with figuring out how to redirect the compiled JS\r\nfiles into a separate temporary directory and causing XCode to point\r\nat that instead of Resources -- since we don't want to override\r\nResources (except in Alloy, that's how it operates, so that's OK).\r\n\r\nFourth, I'm using a uglify-based compiler by hand instead of using\r\nBryan's titanium code processor. The current implementation of the\r\ncode processor doesn't allow you to mutate code in his plugins (or in\r\nthe processor itself). Ideally, this would actually be a code\r\nprocessor plugin that would simply mutate code in a pipeline during\r\ncompilation.\r\n\r\nFifth, the current prototype is iOS only. This is because I'm most\r\nfamiliar with iOS and don't know the Android compiler / bridge for V8\r\nas well as the old version. However, I think this same concept works\r\nfor Android conceptually.\r\n\r\nSix, since I'm not using the code processor, I'm not using the JSCA\r\nfile to understand the API. Moving to the code processor eliminates\r\nthat issue.\r\n\r\nThe last overall limitation is that we might want to actually\r\npre-compile out the jump tables and use actually compiled function\r\ncallbacks instead of the TiCompiledMethod object. If we moved to more\r\nof a true compiled / generated code, that would be rather trivial.\r\nThis would likely give us even more speedup and much less object\r\ngarbage and much lower memory. You would simply generate the C\r\nfunction that would directly call the module and method instead of\r\nhaving to have a generic TiCompiledMethod and you could by pass the\r\nentire Kroll overhead. I believe the new V8 compiled bindings do\r\nsomething similar in Android.\r\n\r\nComponents\r\n----------\r\n\r\n\r\n## CLI Hook\r\n\r\nThe CLI hook file is under plugins/ti.compiler/hooks and in the file\r\nti.compiler.js\r\n\r\n\r\n## Module Source\r\n\r\nThe module source for iOS is under compiler_module.\r\n\r\n\r\nNotes\r\n-----\r\n\r\nThe ti.compiler plugin must come after the ti.alloy in tiapp.xml.\r\n\r\n\r\nAfterthought\r\n------------\r\n\r\nI think this concept can be dramatically further expanded to really\r\nbuild a compiler that can pre-process code and optimize, compile and\r\ngenerally make performance a lot faster than it does today.\r\n\r\n## Collapsing properties\r\n\r\nFor example, take the following code:\r\n\r\n\tvar v = Ti.UI.createView();\r\n\tv.width = Ti.UI.FILL;\r\n\tv.height = Ti.UI.FILL;\r\n\tv.backgroundColor = \"red\";\r\n\tw.add(v);\r\n\r\nThis could easily be re-written to be much faster (prior to\r\noptimizations from prototype):\r\n\r\n\tw.add(Ti.UI.createView({width:Ti.UI.FILL,height:Ti.UI.FILL,backgroundColor:\"red\"}));\r\n\r\nAfter optimizations further:\r\n\r\n\tw.add(T$1());\t\r\n\r\n## Multiple invocations\r\n\r\nAnother example that is common, adding multiple objects to a view\r\nheirarchy.\r\n\r\n\tw.add(view1);\r\n\tw.add(view2);\r\n\tw.add(view3);\r\n\r\nThis could be optimized into:\r\n\r\n\tw.add(view1,view2,view3);\r\n\r\nIn this example, passing 3 objects over a view to add to the window\r\nwill be MUCH faster because you go from 3 separate invocations to one\r\nand more importantly, because this method is required to be invocked\r\non the UI Thread, 3 separate UI thread blocks.\r\n\r\nAdditionally, at some point, you could move the same optimized method\r\nonto the native side such as it would turn into the following:\r\n\r\n\tT$1();\r\n\r\nAnd on the native side, it would simply do the work of adding the 3\r\nviews to a window by native references.\r\n\r\n## Subsequent code blocks\r\n\r\nMy ultimate belief is that most of the static code written in Titanium\r\nJS can be pre-compiled and essentially reduced into few JS engine\r\nfunctions.\r\n\r\nTake the following static code block:\r\n\r\n\tvar w = Ti.UI.createWindow();\r\n\tvar v = Ti.UI.createView({width:TI.UI.FILL,height:TI.UI.FILL,backgroundColor:\"white\"});\r\n\tvar b = Ti.UI.createButton({\r\n\t\ttext:\"Hello\",\r\n\t\twidth:TI.UI.SIZE,\r\n\t\theight:Ti.UI.SIZE\r\n\t});\r\n\tv.add(b);\r\n\tw.add(v);\r\n\tb.addEventListener(function(){\r\n\t\talert(\"hello world\");\r\n\t});\r\n\tw.open();\r\n\r\nThis should be able to be reduced in JS code to:\r\n\r\n\tvar r$1 = T$1(), w = r$1[0], v = r$1[1], b = r$1[2];\r\n\tb.addEventListener(function(){\r\n\t\talert(\"hello world\");\r\n\t});\r\n\tw.open();\r\n\r\nWe should be able to take subsequent code blocks that are static and\r\ncollapse them into code blocks in native that can be mapped to one JS\r\nsymbol pointer.\r\n\r\n## String consts\r\n\r\nWe might be able to get much better optimizations in the JS engine by\r\nturning static code strings into JS consts at compile time. In a ton\r\nof cases in an app, you will use inline strings. Its generally faster\r\nto declare them as a const and then use the const variable name\r\ninstead. This could be a simple optimizations done by the compiler.\r\n\r\n## Dead code removal\r\n\r\nWe need to remove dead code or unused variables and functions. For\r\nexample, in Alloy in the alloy.js file, we define a function named\r\nisTabletFallback. This method isn't used. We could simply optimize\r\non compile and deterine which functions / symbols are reachable and\r\nthen remove them if not.\r\n\r\nFor example, Alloy generates the following:\r\n\r\n\tvar Alloy = require(\"alloy\"), _ = Alloy._, Backbone = Alloy.Backbone;\r\n\r\n\tAlloy.createController(\"index\");\r\n\r\nThis is useful is you need to reference Backbone or underscore\r\nlibraries in the app.js stub. However, in almost all scenarios, the\r\ndeveloper never does this in a standard Alloy app.\r\n\r\nThe code above could be easily reduced to the following if it's not\r\nmodified:\r\n\r\n\trequire('alloy').createController('index')\r\n\r\nThis creates no variables in memory that have to be later garbage\r\ncollected and is much more efficient in execution.\r\n\r\n\r\n\r\n- Jeff\r\n", "attachment": [ { "id": "35249", "filename": "testcompile.zip", "author": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-02-01T18:47:21.000+0000", "size": 6032538, "mimeType": "application/zip" } ], "flagged": false, "summary": "JavaScript-to-JavaScript optimizing compiler", "creator": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "environment": null, "closedSprints": [ { "id": 1058, "state": "closed", "name": "2018 Sprint 16 SDK", "startDate": "2018-07-29T22:26:06.486Z", "endDate": "2018-08-12T22:26:00.000Z", "completeDate": "2018-08-13T17:38:16.757Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "236915", "author": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Come up to speed on Titanium app development.\r\nCollected initial performance data from NBC app running on iOS device.", "updateAuthor": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-02-01T18:51:59.000+0000", "updated": "2013-02-01T18:51:59.000+0000" }, { "id": "238509", "author": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "body": "\nProject Requirements:\n\n1. An optimizing JavaScript compiler will be created that reduces\n bridge traffic between the JavaScrtipt interpreter and the native\n platform.\n \n2. Initial optimizations will include:\n\n2.1 namespace-folding for native Titanium object (to eliminate\n repeated key/value lookups across the bridge):\n\nvar message = 'hello'; Ti.API.log(foo) becomes T$1(foo) (as an example)\n\n2.2 pre-bind-methods-and-arguments\n\nTitanium JavaScript functions with constant arguments will have their\narguments pre-bound on the native side of the bridge. For example,\nTi.API.log('foo') becomes T$2().\n\nThe JavaScript constants (e.g. strings, numbers and booleans) that are\npassed to these native functions will be marshaled across the bridge\nonly once and will remain cached for subsequent lookups in a symbol\ntable. This will avoid repeated type conversions (e.g. JSStringRef <->\nNSString, JSValueRef <-> NSNumber, etc.).\n\n2.3 property-aggregation (performed prior to namespace folding):\n\nvar v = Ti.UI.createView();\nv.width = Ti.UI.FILL;\nv.height = Ti.UI.FILL;\nv.backgroundColor = \"red\";\nw.add(v);\n\nis transformed to\n\nw.add(Ti.UI.createView({width:Ti.UI.FILL,height:Ti.UI.FILL,backgroundColor:\"red\"}));\n\n2.4 view-aggregation\n\nAdding multiple objects to a view hierarchy like this:\n\nw.add(view1);\nw.add(view2);\nw.add(view3);\n\nwill be transformed to this:\n\nw.add(view1,view2,view3);\n\n3. The optimizer will be optional and selectively enabled for specific\n customers.\n\n4. A test suite will guarantee that the transformed javascript will be\n remain functionally invariant to the original javascript.\n\n\n5. The collection of bridge performance data will be automated so that\n performance improvements are demonstrable and to allow for the\n identification of regressions.\n\n6. The design will allow for the compiler to target all Titanium\n platforms, but the initial implementation will only target the iOS\n platform.\n\n\nProposed Architectural Components:\n\n1. Modular AST Processing Pipeline\n\n1.1. This will allow for additional optimizations to be added over\n time.\n\n1.2 The design is based on tree grammars that allows for multiple\n passes over the AST.\n\n1.3 There is a separate grammar/production for each optimization.\n\n2. The native components will be written in C/C++ for platform\n portability.\n\n3. The Test Suite will include multiple tests for each optimization.\n", "updateAuthor": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-02-14T02:03:25.000+0000", "updated": "2013-02-14T02:03:25.000+0000" }, { "id": "238648", "author": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Minutes from meeting planning meeting for optimizing javascript compiler\nDate: 2013.02.14 14:00\nIn Attendance: Max Stepanov Bryan Hughes, Blain Hamon, Allen Yeung, Vishal Duggal, Josh Roesslein\n\n- Adding a pure js wrapper would benefit all platforms, but is a longer term project and not feasible in next 6-8 weeks.\n- Security is a concern during the pre-compile since js constant strings could contain sensitive data (e.g. passwords,etc.)\n- Property aggregation should include applyProperties\n- Pre-binding doesn't require all arguments to be constants. The constant arguments can be pre-bound via the symbol table while dynamic arguments are passed as-is.\n- Crittercism concern since source code is changed. Disable for now (like magnification) until source maps are supported.\n- Provide different optimization levels instead of just off/on, e.g. -O, -O1, -O2, etc. Be prepared that aggressive optimization may change runtime behavior.\n- The code processor could be integrated to inform the optimizer, but this is a longer term project. For example, the cp could tell the optimizer about \"var v = w\" aliases.\n- Add a JIRA ticket for 1.4 above (i.e. view-aggregaton) for platform parity since this is currently only implemented on iOS.\n", "updateAuthor": { "name": "mlangston", "key": "mlangston", "displayName": "Matt Langston", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-02-15T00:19:51.000+0000", "updated": "2013-02-15T00:19:51.000+0000" }, { "id": "439727", "author": { "name": "emerriman", "key": "emerriman", "displayName": "Eric Merriman ", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Closing old \"Won't fix\" tickets. If you disagree, please reopen.", "updateAuthor": { "name": "emerriman", "key": "emerriman", "displayName": "Eric Merriman ", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-08-02T22:20:03.000+0000", "updated": "2018-08-02T22:20:03.000+0000" } ], "maxResults": 4, "total": 4, "startAt": 0 } } }