4 ms·
As a VM dev what I find intriguing about these three optimizations is that they are unlikely to be obviated by any VM optimizations anytime soon. - VMs will in
by pizlonator 10y ago
As a VM dev what I find intriguing about these three optimizations is that they are unlikely to be obviated by any VM optimizations anytime soon.
- VMs will indeed optimize the heck out of object allocations, but I think that hoisting out functions to reduce the rate of allocation is always going to be some kind of win. It definitely reduces work on the front-end, so if your app has a lot of code, it's a great idea. Remember, the VM may optimize away allocations once your code gets super hot and you end up in the optimizing JIT - but that doesn't happen for a lot of web framework code since it's only used for brief interactions. Avoiding allocations in that code is still worthwhile, because the more of that you do the lower the probability of a GC during page load or other important interaction. I'm surprised that function object allocation in particular was a hot spot for them, because I've struggled to find benchmarks where this is a huge deal. But I still think that optimization of theirs is a solid idea and it will continue to be a good thing for as long as I can foresee.
- Rolling your own bitfields is always a win. The proof burden for the VM to do this optimization for you is very high, so it's totally not like register allocation or some other optimization that reliably Just Works. If a VM did infer that a cluster of boolean fields could be compacted into a bitfield, then this optimization would likely either happen late (so it wouldn't benefit short-running code) or it would incur early costs (so the presence of the optimization would penalize all code, including code to which the optimization does not apply). WebKit can infer that properties are boolean, but it won't build a bitfield out of your properties because of these concerns - the benefit is low and the cost is high. Also, lets not forget that this optimization is still not done by much more mature languages like C++. Last I checked, JVMs also didn't do this for you, because concurrency means that this optimization would be a perf fail in the common case. So, if you're writing code and you see an opportunity for a bitfield, please roll it yourself! That's a great idea and it will continue to be a great idea for a long time to come!
- The no-op function trick is amazeballs. This isn't working around the VM - this is working with the VM to get the VM to perform some code specialization for you. I would equate it to using templates in C++ to specialize your code for two distinct cases (bad thing doesn't happen vs bad thing does happen, for example). Except here, you can specialize a lot of different things simultaneously. At least in WebKit, this will scale because even if you start overwriting the no-op functions, we'll recompile with exponential back-off, so the worst case isn't bad. Also, the back-off means that if you overwrite multiple no-op functions at a time, it will probably only cause one recompile because we'll wait before recompiling. The no-op function thing is just smart hacking, and I'm super happy that people are doing this in JS. Also, I'd be curious to hear exactly how anyone would think that this is a worse idiom than any other even ignoring perf. The hook that starts empty and is replaced with non-trivial functionality feels to me like an idiomatic use of the functional part of JS.
- samth 10y ago- I'm surprised that no current JS VM does lambda lifting (which is the transformation being done manually here). The analysis is easy, the wins are big for function-heavy code, and the tech is easy and well-known. JSC should try it! ;) - I was just last week sketching such an optimization, but in a more complicated case, and with types to guide you. Seems very hard to do in JS.
- pizlonator 10y agoWe do most of lambda lifting. The part we don't do is to address the observability of function identity: function foo() { function bar() { } return bar; } foo() !== foo() // this must be true Therefore, we will at a minimum allocate a function object that just says "hey man I'm not like any other function!". We could avoid the allocation if: - We proved that no identity operations ever happen. That's hard! - We used some other UID system to identify different functions. But that would need a GC anyway!
- samth 10y agoProving that identity isn't needed here is easy: if the function is only used in a first-order way (ie, it's always called) then the identity can't possibly matter. That should work in most cases that matter.
- pizlonator 10y agoYou're right, it's easy in strict mode. I hadn't realized this before! In sloppy mode, there's function.arguments. You can walk the stack to find the damned function instance.
- samth 10y agoIn that case, the only identity has to come via function.arguments. Could you therefore allocate the identity lazily, since there's nothing else to compare to?