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