On 03/13/2016 01:06 PM, Brian Barnes wrote:
This is a good time to bring up the other half of my original email because a
number of other people have chimed in with their experiences with GC when
attempting to develop more time critical applications without stutter.
I really don't think you want a System.gc() call for that. What if you
call that after you're placed in a background tab, when you're sharing
the JS heap with a latency-sensitive foreground tab? Do you really want
to stutter the foreground tab for (up to) a few seconds? Probably not,
in which case the name System.gc() would be a lie.
I think the closest you would want to go is to offer hints to the
runtime. AppIsIdleStartingNowForAtLeast(500)?
IDoNotMindIfYouDontCallMeAgainFor(500)? (units are milliseconds). The
runtime can ignore it, or it can schedule an up-to-500ms incremental GC
slice, or whatever. That does not negate all of the issues Boris was
referring to, but IMHO it's a reasonable middle ground. We could
double-check it against pending timers or whatever.
The second part was a hint to tell the engine to always take the most
aggressive route with optimization; for instance, in Safari’s engine, as I
remember, I think there are three levels, interpreted, a half-n-half solution,
and an actual full compile of the code. This hint would say “always compile to
native” or if an engine never goes that far, always compile to the VM soup
(though I suspect at this point most engines can do a native version.)
That's a nice simple mental model, but it's inaccurate in some important
ways.
This would be used, again, for trading time in one place for time in another.
A longer start-up time is something you want for a thing that will run
continually and will almost always be guaranteed to fall into the compile path
eventually. It’s not something you’d want for javascript that runs on a normal
button click to post a form.
But you may *need* to run it in a slower mode 1 or more times in order
for that native compilation to be effective. The fastest JIT levels
compile the code under certain simplifying assumptions to make the
generated code more efficient -- it may assume that you're not going to
randomly add or delete a property from an object handled by the compiled
code, for example, so it can compile in direct offsets to the object's
slots. If you violate one of those assumptions, the compiled code will
need to be discarded and re-generated with a new weaker set of assumptions.
So you don't want to go straight to the highest optimization level, as
it might then not optimize as highly. You need to first run in a slower
mode, perhaps even one that pays some overhead in order to gather
information about the types and control paths actually used. And that's
the next gotcha -- it's reasonable to skip collecting that info on the
first time (or few times?) you run the code, because the majority of
code is only run once so any gathered profiling info is useless.
Which is not to say that there's nothing useful you can tell the system.
If you hinted to it that something was going to be important and run
frequently, then it could choose to gather profiling information earlier
and additionally lower the threshold for jumping up optimization levels.
As with the GC case, though, you do *not* want to tell the VM exactly
what to do. The best approach might be something like: spawn off a
background compile, and in the meantime interpret the code without
gathering profiling info, then switch to the compiled profiling code
when it's ready, and then do an optimized compile based on that
information as soon as you've observed "enough". The user code has no
way to know how long that background compilation will take, whether
there are spare resources to do it in the background, etc. And it varies
by platform. So at best, I think you can drop hints like "this code is
going to run a lot, and I'm pretty sure its runtime matters to me." In
practice, people will end up cutting & pasting the magic bits that make
things fast from stackoverflow and misapplying them all of the place, to
the extent that engines might end up just completely ignoring them, but
it may also turn out that they're a good enough signal that engines will
pay attention. I don't know; I can't predict.
Being compiled gives you another benefit, say you have this class:
class ….
{
test()
{
let x,y,z;
…
….
return(x);
}
The locals y and z are never move beyond the function scope; in this manner
they could be stack based variables that never touch the heap (yes, I know
that’s probably a very difficult implementation.) Or even variables that fall
into a special section of the heap that only deals with locals that never scope
outside the function and are always cleaned up automatically on function exit
(basically, reference counting where you know the reference is always 0.)
I'm a little confused about variables vs values here, but simply storing
a value into a local does not guarantee that the value is only
references by that variable. You can sometimes do an escape analysis to
figure this sort of thing out, and I believe many engines do this or are
working on doing this. But either way, requesting that something be
*compiled* is absolutely the wrong signal -- analysis is separate from
compilation, and in fact information necessary for an analysis is less
likely to be collected at the highest optimization levels. (It would
either be figured out when initially compiling to bytecode or whatever,
or dynamically gathered during early or mid-tier optimization levels.)
This will basically reduce the amount of GCs by a good bit. NOW, you’d also
have to track any additional function calls that use these variables (to make
sure they aren’t save out to a global.) So everything underneath would be a
force compile for this to work. Engines might already do this or stuff like
it, and if so, great!
They do stuff like this, but it's independent of (and complicated by)
compilation.
This is probably something that is *much more* complicated to implement that
most of us might realize, but it’s a thought. Just being able to say “this
class is always compiled” would be nice.
I don't think so. It's not even really the right level of granularity.
Some class methods should be compiled while others are interpreted, for
the lowest overall time.
The second part of this is native primitive types; having int/etc means they
can be passed by value which means these checks are easier, but that’s probably
something others have argued back and forth for a long time :)
The dynamic profiling can figure out whether *in practice* particular
values are always int/etc while certain code executes, and compile with
that assumption. Declaring types can give that a head start, but it'll
still need to be double-checked when not easily statically provable, and
may end up just wasting time prematurely compiling code with incorrect
assumptions. JS right now is simply too dynamic to prevent all
possibility of strange things happening to seemingly simple values.
Besides, just observing the types seems to work pretty well in practice.
The main benefit of type declarations would be in preventing errors via
type checking, IMO.
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss