I'm writing to the Platform list as a developer that maintains a significant amount of JavaScript for Firefox. Much of the JavaScript I maintain is for large backend features, such as Sync and Firefox Health Report. These features are thousands of lines of code (mostly JavaScript) and interact with many systems in Firefox. These backend systems tend to perform many of the same tasks, so there is code shared between them in the form of JSMs. To reduce redundant code (therefore bugs) and development costs, there is obviously the incentive to share as much code as possible.

I'd like to start a discussion about an issue that has been troubling me for a while: the performance overhead of separate compartments per JSM. I've previously brought this up in bugs and with a few individuals. I wanted to raise awareness by posting here. I apologize in advance if I make a few semantic mistakes on things like "globals" and "compartments" and the overhead of cross-compartment operations: I'm not a core platform hacker!

Ever since Compartment per Global (CPG) landed, JS modules imported with Cu.import now get their own compartment (at least on desktop Firefox - I'm told they share a compartment on B2G for performance reasons). This is all nice in theory. You even get a line item in about:memory to see if your module is doing anything wonky!

Unfortunately, this change has a few unfortunate side-effects:

a) Memory overhead for each compartment
b) Perf losses due to crossing compartments

Assuming the about:memory compartment numbers are correct, each imported JS module seems to consume at *least* ~55kb (numbers are similar on both OS X and Windows Nightly builds).

Furthermore, there appear to be some scenarios where crossing compartments has a horrible effect on performance. For example, it is my understanding that crossing compartments can sometimes copy data. I believe this always holds for strings. See bug 806087 for example. Hundreds of MBs of "string-chars/huge" are seemingly being copied between JSMs/compartments. While this specific bug number is resolved, the underlying copying issue remains AFAIK.

Sadly, these two issues have a negative influence on how I write JavaScript code.

I'm of the camp that tends to write many, small, standalone JSMs rather than large and monolithic ones. I find the resulting code is easier to comprehend and test. As an added bonus, anybody can come along later and reuse an individual module. This includes add-ons. Yay code reuse! The end result is less overall code being written and debugged and higher test coverage. I like to think this means the quality of the code base is higher and new features can be rolled out quicker (due to code reuse). I hold strong convictions that this code "architecture" is superior.

With separate compartments per module, I've been put in a tough position. I have to weigh the code maintainability advantages of multiple modules against memory usage and performance overhead. If I create dozens of small, reusable, and specifically-targeted JSMs, I've got the Perf team giving me the stink eye for increasing memory usage and cross-compartment overhead. If I write JSMs that are thousands of lines long, I'm creating technical debt and increasing the barrier to change.

This is a Kobayashi Maru (no win) scenario. Firefox and other Gecko apps suffer with both ends of the spectrum. Finding a compromise between the two extremes is difficult if not impossible in some scenarios. Furthermore, code is always changing thus the conditions influencing the decision are always changing.

That's my problem. And, it's a problem that every large JavaScript feature faces. And, with groups like Jetpack talking about landing lots of reusable JSMs in the tree, I think it's a problem that will only get worse over time.

Now, what can we do about it?

I'd like to live in a world where you are not significantly penalized for creating multiple, loosely coupled JSMs. Here are some ideas:

* Reduce the memory overhead of compartments (I believe this is already being worked on). * Have all or most of chrome-privileged JS share the same compartment (like on B2G). It's my understanding the CPG decision was largely driven by content/security requirements and chrome just got caught up in it. * Eliminate excessive copying and other perf issues associated with sending objects across compartments.
* Allow JSMs to be imported into specific named compartments.
* (Hacky) Build system magic to "concatenate" or merge multiple files together so they load as one module/compartment. This must be done with care because there could be things like file-level symbol collisions.

I requested named compartments in bug 807205. In summary, there are some large JS services (like Sync and Firefox Health Report) that consist of a large number of JSMs. The memory overhead quickly amounts to several hundred kb, possibly into the MBs. All modules are used together and their primary use case is the feature they are written for. If e.g. the Sync XPCOM service could create a "sync" compartment and proceed to import all these modules there, that would significantly curtail both perf impacts of using multiple JSMs.

Another scenario worth mentioning is the shared generic service JSM. For example, I recently wrote Sqlite.jsm. It contains a promise-based API on top of Storage/SQLite that makes interacting with SQLite much, much more pleasant. While it is only currently used for Firefox Health Report, presumably other users of SQLite will start using it because of its friendlier API. If we load these generic modules into their own and only their own compartments, we still have overhead of objects traversing compartment boundaries. In the case of Sqlite.jsm, that could be SQL query results. For something like RESTRequest (a generic necko-based HTTP client library), that's HTTP request and response bodies. These are not trivial, limited use compartment crossings! In the case of bug 806087, we saw hundreds of megabytes in allocations. I suppose one solution would be to allow separate JSMs instances in separate compartments. There will be some redundancy. But, this should be cancelled out by eliminating cross-compartment overhead (at least for heavy use modules).

Those are just my ideas. I'm not a Platform dev. I'm sure some of the ideas are just plain silly. I know there are ideas I haven't thought of.

What do you think? Can we solve this problem?

Gregory
_______________________________________________
dev-platform mailing list
dev-platform@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-platform

Reply via email to