Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
119 lines (99 sloc) 7 KB

Motivation

V8 Optimization

For our project, we created a tool for Javascript authors. Since modern web applications make heavy ues of Javascript for basic functionality, modern web browsers contain very fancy Javascript engines designed to maximize performance. Engines JIT Javascript code to native code and also dynamically optimize the machine code.

However, due to the loose and freewheeling nature of Javascript, many common Javascript programming paradigms create optimization barriers, though often these barriers can be overcome. For instance, in Google’s V8 engine, Javascript objects are compiled to C++ classes to allow very quick access to object fields. This is much faster than using a hashtable to resolve field names to locations in memory. Hoewver, whenever a Javascript author adds a new field to an existing object, V8 must create a new C++ class, as C++ classes cannot be changed like a hashtable can be.

As another example, V8 attempts to inline function calls, but won’t inline if the target fuction’s text is too large. However, when calculating the size of the target function, V8 includes comments! A frequently invoked Javascript function with a large comment therefore may not be inlined while the same function without the comment may be.

As a last example, V8 doesn’t made no attempt to optimize statements contained within try-catch blocks; such code is simply executed slowly. However, if the try block contains some compute-heavy code that can be easily moved to a separate function, refactoring the compute-heavy code into a separate function allows V8 to optimize the subsidiary function, essentially eliminating the try-catch penalty.

To this end, our tool seeks to help Javascript authors identify optimizations barriers in their programs. Such barriers can usually be easily rewritten to a semantically identical but optimizable form.

Instrumentation

Most instrumentation efforts have focused on instrumenting at the level of machine code or static source code. There are good reasons for shying away form instrumenting dynamic code; constructs like eval and on-the-fly anonymous function generation pose challenges to instrumenting all code.

However, there are many use-cases where instrumentation of dynamic code is desirable. We discovered some of them by working on V8 optimization issues. To determine if an object has properties added after being initialized, it is desirable to be able to instrument every line where the object is mutated to see if the list of properties the object possesses change as a result of the line running.

Furthermore, we believe that this is broadly-useful functionality that can help with debugging, monitoring of large-scale deployed websites, and more.

Tools

We built an optimization-barrier diagnostic tool around the Esprima parsing library, a JS parser writen in JS.

We also initially used an Esprima derivative product called Esmorph, a JS mutation tool usable as an instrumentation framework. However, Esmorph is a fledgling project of under 100 LOC, so we unsurprisingly found it to be extremely limiting. As a result, we extended it significantly.

Contribution

To implement our optimization-barrier diagnostic, we provide a Javascript instrumentation framework. We took the existing Esmorph Javascript instrumentation tool and extended its API to provide more general functionality. Esmorph originally could only instrument by inserting code at function beginnings and ends. Note that this is ends of functions, not all exits. Thus, the functionality of instrumenting ends is only really useful for instrumenting void functions with no side exits, an extreme limitation.

We extended the API to be able to insert instrumenting code before and after lines. We allow targeted instrumentation as well; for example, instrumenting every return statement, or instrumenting every if statement (before it, after it, or in its consequent blocks).

We also believe that our combination of static and dynamic checking is an underused idea that will become increasingly important as more work is done with dynamic languages. By reporting only optimization hurdles that are actually encountered when the JIT attempts to compile and optimize code, we can suppress issues in code that never runs enough to be JIT-ed and focus the programmer time on the highest-value optimization opportunities.

This also would allow a system to find bugs and other anomalies most effectively. Some bugs should be found totally statically, some can be found statically but should be filtered based on whether the JIT actually encounters them. But some, especially in a dynamic language, must be caught at runtime. (A flexible code instrumentation tool is critical for instrumenting the code to find these.)

We certainly did not invent this idea. Profile-guided optimization is a similar idea, but we have extended it beyond the realm of compilers looking to make optimization decisions.

Specifics

Try / catch

As of October 2012, V8 didn’t even try – ha – to optimize try/catch statements. However, as mentioned before, compute-heavy code refactored into a separate function called from within the try/catch can still be optimized. If V8 reports a blocked inline due to a try/catch, we walk the AST emitted by Esprima looking for calls to the function that couldn’t be inlined and test if they’re contained within an offending try/catch statement.

Adding fields to objects

Using the line instrumentation facility that we added to Esmorph, we created a small tool that examines all expressions, assignments, and [] field selections to determine when Javascript programs add new fields to extant objects dynamically. This practice incurs a performance penalty as engines like V8 must create a new C++ class to represent the object with a new field. Moving these dynamic field additions outside of performance sensitive inner loops and functions thus present an easy way to extract more performance with little source modification.

Understanding paths through functions

Esmorph includes a demo that counts how many times each function is called. However, there is no way to count which path through the function is taken, due to the shortcomings of Esmorph discussed above. With our extensions, a similar amount of code can give more understanding. As an example, we have implemented a tool that instruments each return statement to count how many times each return is taken.

Citations

Thomas M. Conte, Burzin A. Patel, Kishore N. Menezes, and J. Stan Cox. Hardware-Based Profiling: An Effective Technique for Profile-Driven Optimization. International Journal of Parallel Processing, 24(2):187–206, 1996.

Florian Lotisch. Optimizing for V8 - Introduction. http://floitsch.blogspot.com/2012/03/optimizing-for-v8-introduction.html

Gregor Richards, Sylvain Lebresne, Brian Burg, and Jan Vitek. 2010. An analysis of the dynamic behavior of JavaScript programs. SIGPLAN Not. 45, 6 (June 2010), 1-12. DOI=10.1145/1809028.1806598 http://doi.acm.org/10.1145/1809028.1806598

Chris Wilson. Performance Tips for JavaScript in V8. http://www.html5rocks.com/en/tutorials/speed/v8/