uncovering javascript performance code smells relevant to ... ·...

20
Uncovering JavaScript Performance Code Smells Relevant to Type Mutations Xiao Xiao 1 , Shi Han 2 , Charles Zhang 1 , and Dongmei Zhang 2 1. The Hong Kong University of Science and Technology {richardxx,charlesz}@cse.ust.hk 2. Microsoft Research {shihan,dongmeiz}@microsoft.com Abstract. In dynamic typing languages such as JavaScript, object types can be mutated easily such as by adding a field to an object. However, compiler optimizations rely on a fixed set of types, unintentional type mu- tations can invalidate the speculative code generated by the type-feedback JIT and deteriorate the quality of compiler optimizations. Since type mutations are invisible, finding and understanding the performance issues relevant to type mutations can be an overwhelming task to programmers. We develop a tool JSweeter to detect performance bugs incurred by type mutations based on the type evolution graphs extracted from program execution. We apply JSweeter to the Octane benchmark suite and identify 46 performance issues, where 19 issues are successfully fixed with the refactoring hints generated by JSweeter and the average performance gain is 5.3% (up to 23%). The result is persuasive because those issues are hidden in such well developed benchmark programs. 1 Introduction JavaScript has become a pivotal building block for web and mobile applications. As a dynamically typed language, considerable academic and industrial effort is invested to optimize its performance. One of the important techniques that contributed to the dramatic improvement of the speed of JavaScript is the type- feedback Just-in-time (JIT) compilation adopted by almost all modern JavaScript engines. The type-feedback JIT is a speculative technique that leverages the runtime information to generate fast code and use it in future executions if types remain unchanged [15]. Therefore, unlike statically typed languages, programmers of dynamic languages such as JavaScript can significantly influence the success rate of the speculations. If the code conforms to some coding idioms such as asm.js [1] to restrict the type generation and variation, the type-feedback speculations, along with all dynamic optimization techniques, can be very effective. The underlying reason is that JavaScript engines such as V8 employ two contradictory designs in dealing with types: The fat type design and the type equality testing for validating speculations. The spirit of fat type design is binding certain instance specific

Upload: others

Post on 22-Jul-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Uncovering JavaScript Performance Code SmellsRelevant to Type Mutations

Xiao Xiao1, Shi Han2, Charles Zhang1, and Dongmei Zhang2

1. The Hong Kong University of Science and Technologyrichardxx,[email protected]

2. Microsoft Researchshihan,[email protected]

Abstract. In dynamic typing languages such as JavaScript, object typescan be mutated easily such as by adding a field to an object. However,compiler optimizations rely on a fixed set of types, unintentional type mu-tations can invalidate the speculative code generated by the type-feedbackJIT and deteriorate the quality of compiler optimizations. Since typemutations are invisible, finding and understanding the performance issuesrelevant to type mutations can be an overwhelming task to programmers.We develop a tool JSweeter to detect performance bugs incurred by typemutations based on the type evolution graphs extracted from programexecution. We apply JSweeter to the Octane benchmark suite and identify46 performance issues, where 19 issues are successfully fixed with therefactoring hints generated by JSweeter and the average performance gainis 5.3% (up to 23%). The result is persuasive because those issues arehidden in such well developed benchmark programs.

1 Introduction

JavaScript has become a pivotal building block for web and mobile applications.As a dynamically typed language, considerable academic and industrial effortis invested to optimize its performance. One of the important techniques thatcontributed to the dramatic improvement of the speed of JavaScript is the type-feedback Just-in-time (JIT) compilation adopted by almost all modern JavaScriptengines. The type-feedback JIT is a speculative technique that leverages theruntime information to generate fast code and use it in future executions if typesremain unchanged [15]. Therefore, unlike statically typed languages, programmersof dynamic languages such as JavaScript can significantly influence the successrate of the speculations.

If the code conforms to some coding idioms such as asm.js [1] to restrict thetype generation and variation, the type-feedback speculations, along with alldynamic optimization techniques, can be very effective. The underlying reason isthat JavaScript engines such as V8 employ two contradictory designs in dealingwith types: The fat type design and the type equality testing for validatingspeculations. The spirit of fat type design is binding certain instance specific

Page 2: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

information such as pointer to the prototype to the type. Thus, the JIT optimizerscan perform aggressive optimizations to generate type-specific and more efficientcode. However, a fat type is also fragile that programmers can easily mutate itunconsciously, such as changing the prototype of a function. Therefore, the failurerate of type equality testing, which is the key component to validate speculativeassumptions for JITed code, can be high.

(a)

1 function Foobar ( ) 2 this . abc = 1 ;3 this . t e s t = function (n) 4 this . abc = n ;5 ;6 7 Foobar . prototype . runTest =8 function (N) 9 f o r (var i =0; i<N; ++i ) 10 this . t e s t ( i ) ;11 12 ;13 var N = 10000000;14 (new Foobar ( ) ) . runTest (N) ;15 (new Foobar ( ) ) . runTest (N) ;

(b)1 Foobar . prototype . t e s t =2 function (n) 3 this . abc = n ;4 ;

Fig. 1. The “Foobar” Objects created atLine 14 and Line 15 have different typesdue to the method binding optimization fortest field.

Figure 1(a) extracted from V8’suser group 1 illustrates a case wherethe two “Foobar” objects created atLine 14 and Line 15 have different typeson Google’s V8 JavaScript engine, eventheir allocation sites are literally thesame. The reason is that the field as-signed to a closure instance such as test(Line 3) is stored in the type descrip-tor rather than in the object instance.This is called method binding, becauseV8 recognizes that a field referring toa closure rarely changes [4]. When call-ing Foobar again at Line 15, test isassigned to a different closure instanceand V8 cancels method binding for test.Therefore, the type of the first “Foobar”object is unequal to the type of thesecond “Foobar” object. As a side ef-fect, the runTest function optimizedagainst the first “Foobar” object will

be invalidated when it operates on the second “Foobar” object. A quick solutionis moving the field test to the prototype of FooBar as shown in Figure 1(b). Thissimple change gains 10× speedup.

In this paper, we present a technique that can automatically recognize theperformance code smells relevant to unintentional type mutations and generatea sketched execution path for programmers to understand the smell. Moreover,refactoring hints for programmers to eliminate the type mutations are alsogenerated. For our goal, conventional profiling techniques offer insufficient help.One could use timing functions to find the expensive code fragments. However,this gives programmers a very coarse view of performance symptoms, whichcannot be used to distinguish the performance issues incurred by type mutationsfrom other causes. Since type is implicitly represented by the JavaScript engine,programmers need to link the clues from the engine logs of internal events, theJITed code, and the source code, to understand how types are generated andevolved. This bug hunting process is overwhelming for application programmers.Moreover, the engine logs vary from one engine to another. These factors make

1 https://groups.google.com/forum/#!topic/v8-users/Ofc_SmwDCUM

Page 3: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

application programmers inhibitive to understand how type mutations impactperformance.

InferAction 1Action 2Action 3……..

promoFlds f1ordFls f3, f4useMixin o5advFlds f5movMap f6, f7

TraceModel

Fig. 2. Workflow of our algorithm.

We employ program ex-ecution information otherthan timing results to diag-nose performance issues. Theworkflow of our techniqueis summarized in Figure 2.First, we trace the operationsthat could change the typesof objects. Tracing is per-

formed at the engine side, where changes are not required for the traced code. Inthe second step, we build the type evolution graphs (TEG), one for all the objectsthat are created by the same constructor. In this way, precise type evolutiontrack for each object may be lost, but we gain the knowledge of how the objectsthat could have the same usage evolve to different types. Third, for each typeequality testing failure, we study the path on TEG between the type expectedby the testing and the type being tested, and match it to one of our predefinedcode patterns drawn from empirical study. If the pattern matching succeeded,we generate a refactoring suggestion.

We implement our algorithm in a tool JSweeter and apply it to the Octanebenchmark suite. Our tool reports 46 performance issues relevant to type muta-tions. By successfully fixing 19 issues, we improve the benchmark score by upto 23%. Since the programs in Octane are all well tuned, finding performancebugs for these programs is challenging and our results are worth mentioning. Insummary, our contributions are:

1. We carefully examine V8 and Firefox bug repositories and identify fivecommon ways to cause performance issues by type mutations. Meanwhile, weidentify six types of code smells that often mutate types unintentionally andconclude seven refactoring approaches to eliminate these code smells.

2. We develop an algorithm to detect the performance issues incurred by typemutations based on type evolution graph. Our approach also generates actionablerefactoring suggestions by matching execution patterns to six performance issues.

3. We implement a tool JSweeter and apply it to the benchmark suite Octane.We find 46 performance bugs and 19 of the 46 issues are successfully fixed. Theaverage speedup is 5.3% and one has significant 23% speedup.

2 Types in Type-feedback JavaScript Engine

2.1 Type Collection

Due to lack of types, JavaScript programs cannot be compiled to fully optimizedbinary code ahead of time. Type-feedback is a profiling technique that dynamicallycollects type information for variables [16]. The type information is fed to theJIT compiler for generating efficient speculative code. Type-feedback JITs arepervasively used by all modern browsers such as Firefox and Chrome.

Page 4: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

(a)

1 function t e s t ( a , b)2 3 c = a + b ;4 return c ;5 6 t e s t ("foo" , "bar" ) ;7 t e s t (1 , 2) ;

(b)

1 function t e s t ( a , b)2 3 i f ( i s_s t r ( a ) && i s_s t r (b) )4 c = s t r c a t ( a , b) ;5 else6 c = runtime_plus (a , b) ;7 return c ;8

Fig. 3. Inline cache example. is_str(s)tests if s is a string. strcat concatenatestwo strings. runtime_plus is a runtimefunction to interpret the “+” operator.

The first step for type-feedback op-timizations is type collection. Types areneeded for interpreting the operators thathave multiple semantics. For instance, the“+” operator could be applied to bothnumbers and strings. Inline cache (IC) isan effective way to collect the types andspeedup the execution of the operatorswhose semantics depend on the types oftheir arguments. IC dynamically weavesthe fast paths for observed types into thebinary code [14]. An example is in Fig-ure 3 (a), after the first call to test isexecuted (Line 6), a fast path for process-ing string is embedded into the code. Weshow a proof-of-concept implementationof IC in Figure 3(b), where the if state-ment is called type guard. When we call

test again with string arguments, the fast path will be taken. If “a” or “b” areintegers next time, such as in Line 7 of 3(a), the else branch is taken and theslower runtime function runtime_plus is called. After processing the integerarguments, a fast path for the integer type is also built, resulting in a polymorphicIC (PIC). Types are collected in the way of continuously patching the ICs and aJavaScript engine often provides sufficient warm-up time for type collection.

2.2 Type Mutations

TD1 TD2

TD3

TD4

this.test = ...o2: abc abc, test

o1: abc abc, test

this.abc = 1

this.abc = 1 this.test = ...

A: new Foobar@L13

B: new Foobar@L14

Fig. 4. Type evolution graph. Dashed arrow points tothe type descriptor of corresponding object. Shadowedarea is the type evolution graph and the type mutationthat generates TD4 is highlighted.

Inside JavaScript engine, ev-ery piece of memory, suchas an object, array, string,and closure, is associatedto a type descriptor (TD),which is also known as hid-den class in V8, shape inIonMonkey, and structure inJavaScriptCore. A type de-scriptor records certain in-formation for correctly infer-ring the code behaviors suchas field access. For example,a type for an object usuallycontains fields descriptors that describe the value type (e.g. integer or double) ofeach field and fields layout that records the offset of each field.

Type descriptor should be immutable to guarantee the deterministic behaviorfor the code operated on. Therefore, a type mutation operation that changes anyinformation in the descriptor, such as adding a field to an object, derives a new

Page 5: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

type descriptor. The set of type mutations from the same source type form a typeevolution graph. Figure 4 shows a type evolution graph for our running example(Figure 1), where the flows with labels A and B illustrate the “Foobar” objectso1 and o2, created at Lines 14 and 15, respectively. From the figure, we observethat o1 and o2 share the first type mutation TD1→ TD2, since the statement“this.abc = 1” has the same effect on the type mutations in both executions. Lateron, due to the binding of different closure instances to the same field “test”, o1and o2 are evolved to different types TD3 and TD4.

2.3 Why Type Mutations Impair Performance

Type mutations create new types. A large volume of types render the JavaScriptengine very difficult to generate a unique piece of code that works optimally onall types. As such, programs are falling back to run with conservative runtimestrategies, which are summarized as follows.

Trigger Deoptimization . Unnecessary deoptimization is a major source ofperformance degradation. If a hot function cannot constantly work with optimizedcode, its performance can be orders of magnitude worse. Moreover, frequent typechanges can result in optimization-deoptimization churn and finally disable theoptimization opportunity for the type unstable functions.

Trigger IC Fallback . Every IC has limited slots for building fast paths,hence saturating an IC forces some types (perhaps the frequently visited types)to be permanently handled by runtime functions.

Reduce Optimization Strength . PICs are obstacles for JIT optimizers togenerate high quality code. For example, function inlining is precluded, whichis a very useful optimization to enlarge the scope of intra-procedural analysisand optimizations to cross function boundary. PICs also prevent common sub-expression elimination (CSE) and loop invariant code motion (LICM) to eliminateredundant type guards.

Enter Dictionary Mode . Object and array are often used as a dictionary.JavaScript engines adaptively change the backing storage of object and array tohash table in order to optimize the dictionary usage scenario. However, dictionaryis manipulated by runtime functions instead of ICs, thus the fields read, write,and iteration operations slowdown significantly.

Increase GC pressure . Frequently creating and dropping small objectswill increase the garbage collection (GC) frequency. High GC pressure cansignificantly slowdown the execution of program and increase the latency of eachGC invocation, which deteriorates the user experience of interactive programs.

3 Type Mutation Code Patterns In Practice

In this section, we present our findings of learning real performance bugs fromV8 and Firefox bug repositories incurred by type mutations, denoted as V8 andFF respectively. The results are summarized in Table 1. Each row contains abuggy code pattern and several representative real bug cases labeled as FF ID

Page 6: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

ID Trigger De

optimizatio

n

Trigger IC

Fallback

Reduce Opt.

Strength

Enter Dic

tionary Mode

Increase GC Press

ure

1 Always Use New Closure X X X XV8 2206, 2673FF 631911, 642001

2 Inconsistent Field Ordering X X X FF 813425

3 Partially Initialized objects X X X FF 900849

4 Over-filled Object &Sparse Array X X X V8 2734, 3313, 2192

5 Prototype Mutation X X FF 947048, 1041126

6 Integer Overflow X X X V8 2306, 2617

Table 1. Bug patterns that induce type mutations and incur performance issues.

and V8 ID, where ID is the bug number in corresponding repository. We identifysix code patterns that mutate types and incur performance problems. For eachcode pattern, we also give one or more refactoring approaches from Table 2 toavoid the performance issues. These refactoring approaches are concluded fromthe discussion by the programmers in the bug repository.

1. Frequent Closure Creation . Similar to our running example (Figure1), real code often creates a new closure instance before calling that function,in order to achieve better code encapsulation. However, these closure instancescould result in PICs for call-sites that impair the IC efficiency and precludeinlining (V8 2206), confuse JIT and miss code optimizations opportunities (V82673), and increase the pressure of GC (FF 631911).

Refactoring. We can promote the fields that hold closure instances to theirprototypes to avoid frequent closure creation, such as we did in Figure 1. Wecall this refactoring promFlds. If too many fields should be promoted, it isbetter to use the mixin design pattern to construct objects [22]. We call this wayuseMixin refactoring.

2. Inconsistent Field Ordering . JavaScript programs often have differentpaths to construct an object (e.g. by taking different if-else branches), and thesepaths add fields in different orders. For example, FF 813425 reports a real casein pdf.js: A loop randomly adds fields to the objects created from the same place,and thus, makes a hot function recompile for 11 times.

Refactoring. Guaranteeing the fields that are added in the same order canavoid generating type inconsistent objects. We name this refactoring ordFlds).The second suggestion is called movMap: Use a specialized ES6 Map [2] if anobject is intended to be used as a map.

3. Partially Initialized Objects. It is common that fields are graduallyadded to an object during its lifetime. If the object is frequently used beforefully constructed, every time the object transitioning to a new type alwaysdeoptimizes the code generated by the previous types. A dual pattern is that codeis optimized against a fully constructed object. However, a partially initializedobject is occasionally used and it deoptimizes the code.

Page 7: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Approach Abbr. Interpretation

promFlds(f1, . . . , fn) Move the fields f1, . . . , fn to its prototype

useMixin(o) Apply mixin pattern to construct object o

ordFlds(f1, . . . , fn) Add the fields f1, . . . , fn in a fixed order

movMap(f1, . . . , fn) Move the fields f1, . . . , fn to an ES6 map

advFlds(f1, . . . , fn) Add the fields f1, . . . , fn before use

initAry(a) Initialize the array a before use

factorOut(srcL) Factor out the code around the code at srcL

Table 2. Refactoring approaches and the short descriptions.

Refactoring. A good practice is fully constructing an object before using it,such as adding all the fields in the constructor. We call this refactoring advFlds.If a derived object would like to shadow certain fields in the prototype, try tooverride the shadowed fields as early as possible.

4. Fat Object and Sparse Array . Adding too many fields to an objectcan change its backing storage to dictionary, especially adding fields via thekeyed expression “p[f]” gives stronger hints than the named form “p.f” to enablethe dictionary mode (e.g. V8 2734). If the dictionary mode is unintended, thesubsequent access to the object can slowdown significantly (e.g. V8 3313). Forarrays, the code such as “a=[]; a[x]=1;” creates a sparse array with a hole [0, x).If the hole is large enough, the array is also changed to a dictionary (e.g. V82192). Moreover, accessing to a hole element returns a undefined value and itcan invalidate ICs for operations such as “+” [7].

Refactoring. We can apply movMap to eliminate a fat object if most ofthe fields are added outside constructors. The sparse arrays can be eliminatedby initializing the arrays (initAry). If writing to an element beyond the currentarray boundary is needed, try to allocate a large array and initialize it.

5. Prototype Mutation . Prototype of an object can be replaced at runtime.This behavior is popular in web libraries such as JQuery and Zepto. However,changing prototype can disable many JIT optimizations, such as the optimizationfor instanceof operator and inlining the methods in the prototype (i.e. FF1041126). A more thorough discussion on this issue can be found in the bugreport FF 642500.

Refactoring. Applying the mixin design pattern (useMixin) to constructobjects is the best practice if the purpose of changing prototype is to inheritfunctions from different objects.

6. Integer Overflow . JavaScript only supports double data type, butmodern JavaScript engines optimize the computations that only involve integervalues. When a value exceeds integer range, a much expensive double representa-tion such as boxed double used by V8 [10] is enabled (i.e. V8 2306). Moreover, ifan array element overflows, the data for all the array elements will be lifted to amore general representations, as described by Bolz et.al. [5].

Page 8: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Event Name Arguments Interpretation

Object Events

NewObject ctxt , srcL, obj , t Create an object obj at line srcL under calling con-text ctxt with initial type t

NewArray ctxt , srcL, ary , t Create an array ary at line srcL under calling con-text ctxt with initial type t

ChgProto ctxt , srcL, obj , newProto, t Set the prototype of obj to newProto at line srcLunder calling context ctxt and change type to t

NewField ctxt , srcL, obj , f , v , md , t

Insert field f to object obj with value v at linesrcL under calling context ctxtmd=0: f is added via obj .fmd=1: f is added via obj [“f ”]

DelField ctxt , srcL, obj , f , t Delete field f of object obj at line srcL under call-ing context ctxt and change type to t

UptField ctxt , srcL, obj , f , v , t Assign value v to field f of object obj at line srcLunder calling context ctxt and change type to t

AryWrite ctxt , srcL, ary , inx , t Writing to array ary index inx at line srcL undercalling context ctxt and change type to t

RepLift ctxt , srcL, obj , t

The representation of the elements or properties inobj is lifted by executing an operation at line srcLunder calling context ctxt [5]. The new representa-tion has type t

Function Events

DeoptCode func, obj , tid , T1, . . . , TK

The function func deoptimized at an IC id becausethe type t of object obj is not previously collectedby the IC id .The expected types for the IC are T1, . . . , TK .

Table 3. Definitions of the events in the operational log.

Refactoring. If overflow will eventually happen, the best solution is isolatingthe code that are tainted by the overflowed values to a new function, as suggestedby McCutchan [19]. We call this refactoring approach factorOut.

4 Finding Unintentional Type Mutations

We adopt a three-step approach based on program execution information to detectthe unintended type mutations and infer the refactoring suggestions. First, wecapture the type mutations by runtime monitoring and construct type evolutiongraph. Second, we identify the unintentional type mutations by analyzing the typesthat incur deoptimizations. Third, we infer the bug pattern of each unintentionaltype mutation by analyzing the relevant part of the type evolution graph. Therefactoring suggestions are naturally derived from the guidelines for refactoringthe bug patterns in Section 3. More details of these steps are explained in thefollowing sections.

4.1 Modeling Type Evolutions

We instrument the JavaScript engine to collect the operational log, which containsthe type update operations for objects and deoptimization information. Table 3

Page 9: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

defines all the events recorded in the log. Every object event contains the callingcontext information (ctxt) and the source code location (srcL) to precisely locatethe event triggering code. If the value v recorded in the events NewField andUptField is a closure instance, we replace v with the unique ID of the definitionplace of the closure. The most important event is DeoptCode, which contains thetypes (T1, . . . , TK) collected at the deoptimized IC (id) and the object (obj) thatcauses the deoptimization.

With the operational log, we build the type evolution graphs, one for eachallocation source, which is defined as follows:

Definition 1 The allocation source ASo for object o is:• o = new ctor(...): ASo is the constructor “ctor”.• o = or o = []: ASo is the global unique ID that represents this object

literal or [].

We aggregate the objects by allocation source because objects created by thesame constructor or from the same literal likely to have the same usage scenarios,and refactoring can be easily performed at the constructor level. In the rest of thispaper, we call the objects created at the same allocation source sibling objects.The type evolution graph ψ for an object allocation source is defined as follows:

Definition 2 A type evolution graph (TEG) ψ is a 6-tuple (Ω,S, θ,Σ, δ, q0):• Ω is a finite set of types.• S is a finite set of states.• θ: S → Ω is an injective mapping from a state s ∈ S to a type t ∈ Ω. We

name the reversed mapping as θ−1.• Σ is a finite set of events.• δ: S × Σ → S is a type transition function that describes a type update

operation.• q0: the initial state.

The set of type evolution graphs are collectively represented by Γ . Since themapping between S and Ω is injective, we abuse the terms type and state in therest of the paper.

We scan the operational log to generate the type evolution graphs. For everyevent in the log, we process it with Algorithm 1. The main idea of Algorithm 1 isfirst calling GetTEG to find or build the evolution graph for corresponding object.Then, it creates a state transition to reflect the type change. Other sub-proceduresappeared in Algorithm 1 are explained in below:

1. GetTEG(o, newTy): Obtain the TEG for the object o. If o is the firstobject for its allocation source, build a new TEG with initial type newTy .

2. FindState(ψ, o): Locate the state in the evolution graph ψ that con-tains the type of the object o at the moment.

3. AddTransition(s1, t, E): Create a labeled transition s1E−→ s2 to

reflect the type change, where s2 is the state for type t.The type evolution graphs created by Algorithm 1 for our running example is

similar to that in Figure 4. The structure of type evolution graph is a directed

Page 10: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Algorithm 1: UpdateTEGInput: E = An event in the operational log

1 switch E.type do2 case Object Event:3 obj = E.obj;4 newTy = E.t;

/* 1. Find or build an TEG */5 teg = GetTEG(obj, newTy)

/* 2: Build type transition */6 s = FindState(teg, obj);7 AddTransition(s, newTy, E);8 if newTy == Dictionary then9 hint = ProcessDictObj (o);

10 EmitSuggests (hint);11 end12 end13 case Function Event:14 CheckDeopt(E.obj, [T1, T2, . . . , TK ]);15 end16 endsw

Algorithm 2: ProcessDictObjInput: o: The object in dictionary mode

1 if o is object then2 if o has more than Kf fields then3 if CountKeyedAddFlds (o) > 0

then4 SetWatch (o)5 end6 else if o is array then7 evt = last event for o;8 if evt == AryWrite And evt.inx >

o.length then9 if evt.inx ≤ 1,000,000 then

10 return initAry(o);11 end12 end13 end

acyclic graph (DAG), because type evolution cannot go back to an old type.However, two different types can evolve to the same type. For example, alldictionary mode objects have the same type.

If an object is changed to dictionary (Line 8), we infer whether or not thedictionary backing storage is intentional with Algorithm 2. First, we only consideran object with more than Kf (e.g. Kf = 15) fields as a candidate of fat object.Second, if there is at least one field of o added through the keyed expressionsuch as “p[f]” (obtained by CountKeyedAddFlds), we mark the object o bySetWatch. The reason is adding fields to an object through keyed expression“p[f]” strongly implies that the field name “f” is only known at runtime. Hence, ois very possibly to be a dictionary. However, this heuristic alone is not enough,we need more evidence and hence we make decision in Algorithm 5. If the objectis an array, we emit an initAry suggestion if the last event is an out-of-boundaccess and the array size is small enough. Access out-of-bound on a large arrayis very likely to use the array as a dictionary.

4.2 Checking Type Homogeneity

We define that type t1 is homogeneous to type t2 if they belong to the same TEGψ. We use homogeneity to identify the types that are evolved from the sameallocation source. In term of graph reachability, two types can be homogeneous inthree ways. Suppose Rψ is the reachability relation on ψ, where Rψ(x, y) meansthere is a path x; y on ψ. Two types t1 and t2 are homogeneous iff:• Rψ(t1, t2), or• Rψ(t2, t1), or• ∃t3 ∈ Ωψ, Rψ(t3, t1) and Rψ(t3, t2).

Page 11: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Algorithm 3: CheckDeoptInput: o, to: object o with the type toInput: id, [T1, T2, . . . , TK ]: T1, · · · , Tk are the types collected by the IC id

1 nhomo = K;2 Q = ∅;3 so = MapToState (ψo, to);4 for tc ∈ [T1, T2, . . . , TK ] do5 sc = MapToState (ψo, tc);6 if sc is non-exist then // to is not homogeneous to tc7 nhomo = nhomo− 1;8 continue;9 end

// Get the path P and the path distance between to and tc on ψo10 P, d = ComputePath(to, tc);11 if d > 0 then // Rψ(to, tc)

12 Q = Q ∪ HandleFutureType(d, P);13 else if d < 0 then // Rψ(tc, to)

14 Q = Q ∪ HandlePastType(−d, P);15 else // Rψ(ts, to) and Rψ(ts, tc)

16 ts = FindLCA(to, tc);17 Po = Pc = ∅;18 SplitPaths (ts, P , Po, Pc);19 Q = Q ∪ HandleSplitType(ts, Po, Pc);20 end21 end22 if CountDeoptSite(id) > Ks then Q = Q ∪ factorOut(id);23 if nhomo

K ≥ πh then EmitSuggests(Q);

We implement the homogeneity testing in Algorithm 3. The high level work-flow, excluding the details in the if . . . else block from Line 11 to Line 21, ischecking the relationship between type to and type tc, where to is the type of theobject o at the time of causing deoptimization and tc ∈ [T1, T2, . . . , TK ] is a typeneeded by the IC at the deoptimization site. To decide how to is homogeneous totc, we use two auxiliary procedures:

1. MapToState: It is exactly the θ−1 function (recall Definition 2). If thestate for tc is non-exist, tc and to is not homogeneous.

2. ComputePath: It computes the shortest path P between to and tc on ψo.If multiple paths exist, choose arbitrary one. The choice of the path does notmatter, because after the refactoring, we can run the analysis again to studyanother path. The second return value d is the length of P . The sign of d encodesthe path direction: d > 0 indicates Rψ(to, tc). d < 0 represents Rψ(tc, to). d = 0means tc and to are reachable by an intermediate node ts.

We record how many types cached at the IC are homogeneous to to in thevariable nhomo. If the ratio nhomo

K exceeds the threshold πh, we decide to as anunintentional type and output the refactoring suggestions.

Page 12: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Algorithm 5: HandlePastTypeInput: P , d: The shortest path P for tc ; to with distance d

1 E = ∅, R = ∅;2 hasOtherEvents = false;3 if IsDictMode (o) And IsWatched (o) then4 R = R ∪ movMap(o)5 end6 foreach evt ∈ P do7 if evt != NewField then8 if evt == ChgProto then R = R ∪ useMixin(o);9 else if evt == RepLift And NumFields (o) > Ki then

10 R = R ∪ factorOut(srcL);11 end12 hasOtherEvents = true;13 end14 E = E ∪ evt;15 end16 if d ≤ Kd And hasOtherEvents == false then R = R ∪ advFlds(E);17 return R;

4.3 Inferring the Reason of Deoptimization

The if . . . else branch from Line 11 to Line 21 in Algorithm 3 infers bug patternsfrom the path between to and tc on the type evolution graph. Since the pathonly has three cases, our inference algorithm works in three ways:

Algorithm 4: HandleFutureTypeInput: P , d: The shortest path P for

to ; tc with distance d1 E = ∅;2 if d ≤ Kd then3 foreach evt ∈ P do4 if evt != NewField then5 return6 E = E ∪ evt;7 end8 end9 return advFlds(E);

1. HandleFutureType(d, P): Ithandles the case where tc might be atype for object o in future. This case isprobably that o is used before fully con-structed compared to its sibling objects,which is an instance of partially initial-ized objects bug (pattern 3). If d ≤ Kd

and all the events between tc and to areNewField, we emit an advFlds sugges-tion. We typically choose a small valuefor Kd (e.g. Kd = 3), because shorterpath is more likely to be exceptional.All events should be NewField because

advancing the UptField and DelField events are unsafe.2. HandlePastType(d, P): This situation is object o or its sibling objects

have type tc in the past. We examine the evolution path tc ; to to confirmthe bug pattern for o. First, if the backing storage of o is dictionary and o iswatched at Line 4 of Algorithm 2, we deem the object o has refactoring valueand emit a movMap suggestion. Second, if there is a ChgProto event on thepath, we emit a useMixin suggestion. Third, if integer overflows and changesthe value representation (e.g. int → double), we emit a factorOut suggestionif the object has more than Ki fields or array elements. The objects with morefields are potentially accessed in more places and thus, incur more IC failures and

Page 13: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

Algorithm 6: HandleSplitTypeInput: ts, Po, Pc: The paths Po: ts ; to and Pc: ts ; tc// fpos, cls: Mapping from field name to path position and to closure ID

1 fpos = cls = ∅;2 for i← to len(Po) do3 evt = Po[i] ; // Get ith event on the path Po4 if evt == NewField Or evt == UptField then5 v = evt.v;6 if v is closure then cls[evt.f] = v;7 if evt == NewField then fpos[evt.f] = i;8 end9 end

10 proF = ordF = ∅;11 for i← to len(Pc) do12 evt = Po[i] ; // Get ith event on the path Pc13 if evt == NewField Or evt == UptField then14 f = evt.f; v = evt.v;15 if v is closure instance And cls[f] == v then proF = proF ∪ f;16 if evt == NewField And fpos[f] != i then ordF = ordF ∪ f;17 end18 end

19 R = ∅;20 if |proF| > Kp then R = R ∪ useMixin(o);21 else if |proF| > 0 then R = R ∪ promFlds(proF);

22 if |ordF| > 0 then R = R ∪ ordFlds(ordF);23 return R;

create higher performance impact. Finally, same to Algorithm 4, if all the eventsbetween tc and to are NewField and d ≤ Kd, it could be a partially initializedobjects case and we emit a advFlds suggestion.

3. HandleSplitType(ts, Po, Pc): This case states that tc and to deviateto different evolution paths at the state ts, which is the lowest common ancestor(LCA) for tc and to, computed by FindLCA. We first bisect the path into ts ; toand ts ; tc two segments with SplitPaths in Algorithm 6. Then, we scanthe two paths and fill and collect the fields that are assigned to different closureinstances and the fields that are added in different order in the two paths. Thescan results are stored in proF and ordF. We emit a refactoring suggestionuseMixin if proF has more than Kp (e.g. Kp = 7) results, in which case usingmixin pattern is better than promoting many fields to the prototype. Otherwise,if proF and ordF are non-empty, we give the promFlds and ordFlds refactoringsuggestions.

We also count the number of deoptimizations incurred by each deoptimizationsite via CountDeoptSite at Line 22 of Algorithm 3. The counting result tellsus which IC is less stable than others. In case the deoptimization is hard to beeliminated, we emit a factorOut refactoring suggestion, since factoring the codearound the problematic IC site to a new function can limit the performanceimpact to a smaller scope. This is especially useful for performance problemshappened inside a hot loop [19].

Page 14: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

5 Evaluation

We implement our algorithm in a tool JSweeter. Operational log collection isperformed on a modified version of V8. We apply JSweeter to Octane benchmarksuite Version 2. The reason to choose Octane is twofold. First, we only modifyV8, which is incapable to execute the JavaScript programs requiring externalfacilities, such as DOM and AJAX. We did not manage to modify a full functionalJavaScript execution tool such as Chrome due to the excessive hacking efforts.Second, compared to other popular JavaScript benchmark suites such as Krakenand SunSpider, Octane has much larger programs modified from real worldapplications (up to 33,000 LOC for pdfjs) that can prove the effectiveness of ourproposed algorithm for real sized programs. Our experiments are conducted ona machine running 32-bit Ubuntu 12.04 with an Intel Core2 3.0GHz CPU and4GB RAM.

5.1 Overall Results discussion

crypto splay box2d gbemu typescript pdfjs

#Total Issues 4 1 8 12 18 3

#Fixed Issues 3 1 3 5 5 2

Score Before fix 18840 9362 20347 38748 19590 13858

Score After fix 19495 11480 21125 40237 20394 14330

Speedup 3.5% 23.0% 3.8% 3.8% 4.1% 3.4%

Table 4. The benchmark scores before and after fixingthe performance issues.

We empirically choose the pa-rameters πh = 0.5, Kd = 3,Kp = 7, Ki = 25 and runJSweeter. Our findings aregiven in Table 4. The sub-jects that only have marginalimprovements, such as zlib.js,datablue.js, and etc., are omit-ted. We totally report 46 per-formance issues, which is sur-prising since these programs

are well tuned. We successfully fix 19 of these issues that are simple enough tofix in one hour following the refactoring suggestions. The remaining 27 issuescannot be fixed in two reasons:

1. We are unable to understand 21 issues. The major reason is JSweeter onlyrecords one level calling context information for type update events, which isinsufficient to guide us to trace back to the source of bug introducing place,especially we are not the authors of these programs. The benefit is that ourapproach incurs low overhead for collecting execution information. A tool suchas that described by Feldthaus et al. [6] would be helpful and we will explore itin future.

2. We are unable to apply 6 refactoring suggestions (false positives). Thisis because our algorithm is a pure dynamic analysis without considering thestatic program semantics. For example, an advFlds suggests adding a field inthe constructor, but the name of that field is extracted from user input and it isunable to add such fields in advance. Even a field f whose name is statically known,blindly adding f in the constructor can suppress the field existence testing suchas if (p.f == undefined) and possibly change the program behaviour. Moreover,the initial value of the field is sometimes hard to determine. In future work, we

Page 15: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

will consider using static information to weed out infeasible fixes and guide therefactoring.

We measure the benefits of refactoring the programs by Octane score, whichis inversely proportional to execution time and the larger the better. The scoresare obtained by a fresh checkout of V8 (version 3.29.42). For each program, werun it for five times and obtain its average score. All of the refactored programsgain higher scores, where most of the programs only have 3% – 4% speedup andone case splay.js is 23% faster. The results are indicative and cost benefit, sincethe JavaScript engine developers often tried hard and achieve the similar results.It is valuable to mention that JSweeter also found the bug reported in FF 813425bug case. This bug is one of our two findings of inconsistent field ordering bugsin pdfjs. By adding the fields before use, we obtain 2.2% speedup for this singlemodification, very close to the 2.7% speedup achieved by the pdfjs developers.

5.2 Case Studies for Octane

We select five issues from three programs for case study. These cases areselected because each of them represents a different bug pattern. Also, theseissues are difficult to be observed by programmers, since the bug introducingplace and the symptom place are spatially far.

1 SplayTree . prototype . i n s e r t =2 function ( key , va lue ) 3 // ...4 var node =5 new SplayTree . Node ( key , va lue ) ;6 i f ( key > this . root_ . key ) 7 node . l e f t = this . root_ ;8 node . r i g h t = this . root_ . r i gh t ;9 this . root_ . r i gh t = null ;

10 else 11 node . r i g h t = this . root_ ;12 node . l e f t = this . root_ . l e f t ;13 this . root_ . l e f t = null ;14 15 this . root_ = node ;16 ;

16 SplayTree . prototype . remove =17 function ( key ) 18 // ...19 i f ( ! this . root_ . l e f t ) 20 this . root_ =21 this . root_ . r i gh t ;22 else 23 // ...24 25 ;

Fig. 5. splay.js: The unordered addition of fields “left” and “right” in function insertwill deoptimize the function remove at Line 16.

Case 1: splay.js The splay.js program implements the splay tree datastructure, which is primarily designed for testing the performance of memorymanagement. JSweeter finds an obscured performance issue caused by the un-derscored statements in function insert as shown in Figure 5. There is aninstance of the typical inconsistent field ordering problem, where the fields “left”and “right” are added to the “SplayTree.Node” objects in different orders. Asa consequence, when these “SplayTree.Node” objects are accessed, they wouldgenerate PICs and incur additional type checking overhead.

Page 16: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

1 h . prototype . SolveTOI =2 function ( a ) 3 // ...4 f o r ( ; ; ) 5 // ...6 i f (b . m_flags & l .

e_toiFlag )7 c = b . m_toi ;8 else 9 // ...10 b . m_toi = c ;11 12 13 ;

Fig. 6. The simplified code for addingthe field “m_toi” in box2d.js.

1 A. prototype . GetNext =2 function ( ) 3 return this . m_next4 ;5 A. prototype . GetFixtureA =6 function ( ) 7 return this . m_fixtureA8 ;9 A. prototype . GetFixtureB =10 function ( ) 11 return this . m_fixtureB12 ;

Fig. 7. Functions that are deopti-mized by adding field “m_toi” inh.SolveTOI.

Even worse, these objects would deoptimize the remove function throughthe IC site at Line 16. And the consequent performance degradation incurredby using un-optimized version of remove would be prominent, because splay.jsfrequently inserts and removes nodes from the splay tree. Simply adding thefields “left” and “right” in the two conditional branches in the same order wouldfix this problem. A better solution is proactively adding both “left” and “right”in the constructor SplayTree.Node, which also avoids the problems caused bythe SplayTree.Node objects in other places. We obtain 23% more scores fromthis simple fix.

Case 2: box2d.js The box2d.js program is a popular 2D physics engine.It has nearly 9500 lines of deminified code. Since box2d.js is compiled fromEmscripten 2, a C++ to JavaScript compiler, it is full of simply-named variablessuch as “a”, “Q”, and etc.. Thus, finding performance issues manually for box2d.jsis almost impossible even for an experienced programmer. With the help ofJSweeter, we successfully fix three performance bugs.

Among the three bugs, one would incur deoptimizations for seven functionsby adding a field “m_toi”. This field addition operation is performed in functionh.SolveTOI. We show a simplified version of h.SolveTOI in Figure 6, wherewe highlight the two access sites for field “m_toi”: Line 7 is a read site and Line10 is a write site. Line 10 changes the type of the objects referenced by “b”, whichdeoptimize quite a few functions, such as those in Figure 7.

JSweeter outputs a addFlds hint to suggest adding the m_toi field in anearly stage. In the bug report, JSweeter locates function Oa as the constructor ofthe objects pointed by b and the corresponding “Oa” object is in the functionz.Create. However, our first attempt by directly adding the field m_toi followedby the creation of “Oa” object in z.Create does not eliminate the performanceissue. A further investigation with the calling context information shows that thefields of object “Oa” fields are added in functions A.Reset and A.b2Contact.At this place, JSweeter cannot offer more help. Based on our human study of

2 https://github.com/kripken/emscripten

Page 17: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

1 GameBoyCore . prototype . i n i t i a l i z eT im i n g = function ( ) 2 // ...3 this . CPUCyclesTotal = ( this . baseCPUCyclesPerIterat ion − this .

CPUCyclesTotalRoundoff ) | 0 ;4 5 GameBoyCore . prototype . audioUnderrunAdjustment = function ( ) 6 // ...7 this . CPUCyclesTotalCurrent += (underrunAmount >> 1)∗ this . machineOut ;8 9 GameBoyCore . prototype . i te rat ionEndRout ine = function ( )

10 // ...11 this . CPUCyclesTotalCurrent += this . CPUCyclesTotalRoundoff ;12 13 GameBoyCore . prototype . r e c a l c u l a t e I t e r a t i o nC l o c kL im i t = function ( ) 14 // ...15 this . CPUCyclesTotal = this . CPUCyclesTotalBase + this .

CPUCyclesTotalCurrent − endModulus ;16 this . CPUCyclesTotalCurrent = endModulus ;17

Fig. 8. All places that write to “CPUCyclesTotal” and “CPUCyclesTotalCurrent”.

functions near to A.Reset, we realize A.Update is the best place to add thefield m_toi. With this refactoring, all the seven deoptimizations are eliminated.

Case 3: gbemu.js The gbemu.js program is a GameBoy emulator.Unlike box2d.js, which allocates many empty objects and incrementally updatesthem, gbemu.js uses a big monolithic data structure named gameboy to store thevirtual machine states. In this flat design, almost all the fields of gameboy areadded by the constructor GameBoyCore and most of these fields are integers.

One representative issue is caused by the integer overflow of two fields:CPUCyclesTotal and CPUCyclesTotalCurrent. From their names, we guess thesefields store the number of CPU cycles elapsed on the emulated CPU. There areonly four places that write to CPUCyclesTotal and CPUCyclesTotalCurrent otherthan the constructor, summarized in Figure 8.

Taking CPUCyclesTotal as an example, its value can exceed 230 at Line 15of Figure 8, which is the upper bound for the small integer representation usedby V8. The integer overflow triggers a representation change to use double valuefor CPUCyclesTotal. As a consequence, all fields in the object “gameboy” arelifted to double representations [5], and all operations related to these fields areimpacted. As suggested by the factorOut hint, we use a separate object to placethe CPUCyclesTotal and CPUCyclesTotalCurrent fields. In this way, all fieldsare not mutually impacted.

The second issue is that the field mixerOutputCache occasionally gets NaNvia the computation as shown in Figure 9. Since JSweeter does not track thevalue flows, we cannot understand how mixerOutputCache becomes NaN. Wesimply add a NaN checking before assigning the computation result to mixer-OutputCache.

JSweeter also outputs a factorOut suggestion for an anonymous closureassigned to array “LINECONTROL”, which is responsible for screen rendering.In this case, large volume of closure instances are created and they deoptimize163 times, where 90.4% of the deoptimizations are contributed by the field-access

Page 18: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

1 GameBoyCore . prototype . mixerOutputLevelCache = function ( ) 2 this . mixerOutputCache =3 ( ( ( ( this . channel1currentSampleLeftTrimary +4 this . channel2currentSampleLeftTrimary +5 this . channel3currentSampleLeftSecondary +6 this . channel4currentSampleLeftSecondary ) ∗7 this . VinLeftChannelMasterVolume ) << 9) +8 ( ( this . channel1currentSampleRightTrimary +9 this . channel2currentSampleRightTrimary +10 this . channel3currentSampleRightSecondary +11 this . channel4currentSampleRightSecondary ) ∗12 this . VinRightChannelMasterVolume ) ) ;13

Fig. 9. The unique place that writes to “mixerOutputCache”.

1 this .LINECONTROL[ l i n e ] =2 function ( parentObj ) 3 i f ( parentObj . LCDTicks<80) 4 // ...5 6

Fig. 10. The IC site (highlighted area)that contributes most to the deoptimiza-tion of LINECONTROL.

1 function entry0 ( parentObj ) 2 var t i c k s = parentObj . LCDTicks ;3 processLT143 ( t i ck s , parentObj ) ;4 5 function6 processLT143 ( t i ck s , parentObj ) 7 i f ( t i c k s < 80) 8 // ...9 10 11 this .LINECONTROL[ l i n e ] = entry0 ;

Fig. 11. Isolate the deoptimization site withother parts in LINECONTROL.

site at Line 3 of Figure 10. To factor out this problematic IC site, we take atwo-step solution. We first define function entry0 that only reads the fieldLCDTicks and keep other statements in function processLT143. Second, weadd a tail call to processLT143 in entry0, as shown in Figure 11. By thisrefactoring, we assign the unique instance of entry0 to all the elements of array“LINECONTROL”, and this frequent closures creation problem is solved.

6 Related Work

JavaScript Performance Debugging . The most relevant work to us is Gong et.al.’sJITProf [9]. This work also performs a pattern matching based dynamic analysisto locate the code that causes JIT failures and results in performance degradation.However, JSweeter is more general and powerful than JITProf in four ways:

1. Our 6 bug patterns are not ad-hoc: Type mutation is their coherent reasonto cause performance issues. This deep insight can guide programmers to findnew bug patterns easily. Moreover, we also performed an empirical study andshowed the pervasiveness of the proposed bug patterns. In contrast, JITProf onlylists 7 bug patterns without explaining where these patterns come from.

2. Central to our algorithm is the type evolution graph (TEG), which is auniform representation for different pattern matching algorithms. In contrast,JITProf designs individual pattern matching algorithm for each bug pattern,which precludes adding new patterns easily.

Page 19: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

3. TEG aggregates the type information for sibling objects while JITProftraces the state for each individual object. TEG is superior for bug detectionbecause, by contrasting the behavior of an object to its sibling objects, a deviatedtype evolution is more likely to be a real bug.

4. JSweeter is running offline and thus have more flexibility to run complicatedpattern matching algorithms without incurring runtime overhead. For example,Algorithm 5 retrospects the historic type information to confirm the partiallyinitialized objects bug. In contrast, JITProf works totally online and performslimited checks to decide a bug. Nevertheless, JITProf already incurs 18× runtimeoverhead even with events sampling.

Performance debugging on statically typed languages . Most of the works stillrely on function execution time profiling data and statistical algorithms [20, 23,18, 25, 3, 24, 8, 13]. However, as we argued, type mutations cannot be captured bytime profiling results. The works Sherlog [26] and G2 [11] share similarity to ours.Sherlog infers a possible control flow from program start to the symptom site.The control flow information is useful for functional bugs, but it is unknown howperformance bugs can benefit from it. Instead, JSweeter generates an object centricview of the type update process that only contains the operations pertaining toperformance issues. The work G2 also models the log events as a graph and itbackwardly and forwardly to search the root cause. Compared to G2, JSweetergoes further to generate refactoring suggestions by pattern matching the objectsevolution history to our empirical observations.

Avoid type instability with type prediction. Instead of preventing type muta-tions, improving the type prediction successful rate can also speed up JavaScriptexecution. Hackett et al. are the first to design a type-inference algorithm thatworks for full JavaScript features [12], by performing type inference online withthe help of type-feedback. In contrast, Kedlaya et al. use the type-inference toaid type-feedback to intelligently place type profiling hooks [17]. Santos et al. [21]developed a technique to generate a specialized version of the function for everycombination of the parameter values for that function, which significantly enforcesthe power of constant propagation and other optimizations. All these works areorthogonal to ours, because our aim is involving programmers to address theperformance bugs with complex logics.

7 Conclusion and Future Work

In this paper, we propose a dynamic analysis to detect, infer, and refactor sixJavaScript performance issues incurred by type mutations. We first empiricallystudy the performance bug patterns common in real world programs. Based on thestudy, we design a technique that analyzes the type evolution graph to infer theoccurrence of the predefined code smells and synthesize refactoring suggestions.We implement a tool JSweeter and find nineteen performance bugs in Octanebenchmark suite. These bugs can be effectively fixed by following JSweeter’srefactoring suggestions and the benchmark scores for bug fixed programs canincrease up to 23%.

Page 20: Uncovering JavaScript Performance Code Smells Relevant to ... · applicationprogrammersinhibitivetounderstandhowtypemutationsimpact performance. Action 1 Infer Action 2 Action 3

References

1. https://asmjs.org2. https://people.mozilla.org/~jorendorff/es6-draft.html3. Aguilera, M.K., Mogul, J.C., Wiener, J.L., Reynolds, P., Muthitacharoen, A.:

Performance debugging for distributed systems of black boxes. In: SOSP (2003)4. Ahn, W., Choi, J., Shull, T., Garzarán, M.J., Torrellas, J.: Improving javascript

performance by deconstructing the type system. PLDI (2014)5. Bolz, C.F., Diekmann, L., Tratt, L.: Storage strategies for collections in dynamically

typed languages. In: OOPSLA (2013)6. Feldthaus, A., Millstein, T., Møller, A., Schäfer, M., Tip, F.: Tool-supported

refactoring for javascript. In: OOPSLA (2011)7. Flückiger, O.: Compiled Compiler Templates for V8. Master’s thesis (2014)8. Fu, Q., Lou, J.G., Wang, Y., Li, J.: Execution anomaly detection in distributed

systems through unstructured log analysis. In: ICDM (2009)9. Gong, L., Pradel, M., Sen, K.: Jitprof: Pinpointing jit-unfriendly javascript code.

In: Proc. ESEC/FSE. pp. 357–368. ACM (2015)10. Gudeman, D.: Representing type information in dynamically typed languages (1993)11. Guo, Z., Zhou, D., Lin, H., Yang, M., Long, F., Deng, C., Liu, C., Zhou, L.: G2:

a graph processing system for diagnosing distributed systems. In: USENIXATC(2011)

12. Hackett, B., Guo, S.y.: Fast and precise hybrid type inference for javascript. In:PLDI (2012)

13. Han, S., Dang, Y., Ge, S., Zhang, D., Xie, T.: Performance debugging in the largevia mining millions of stack traces. In: ICSE (2012)

14. Hölzle, U., Chambers, C., Ungar, D.: Optimizing dynamically-typed object-orientedlanguages with polymorphic inline caches. In: ECOOP (1991)

15. Hölzle, U., Ungar, D.: Optimizing dynamically-dispatched calls with run-time typefeedback. In: PLDI (1994)

16. Hölzle, U., Ungar, D.: A third-generation self implementation: Reconciling respon-siveness with performance. In: OOPSLA (1994)

17. Kedlaya, M.N., Roesch, J., Robatmili, B., Reshadi, M., Hardekopf, B.: Improvedtype specialization for dynamic scripting languages. In: DLS (2013)

18. Liu, X., Mellor-Crummey, J.: Pinpointing data locality problems using data-centricanalysis. In: CGO (2011)

19. McCutchan, J.: Accelerating oz with v8: Follow the yellow brick road to javascriptperformance. Google I/O Conference (2013)

20. Nistor, A., Song, L., Marinov, D., Lu, S.: Toddler: detecting performance problemsvia similar memory-access patterns. In: ICSE (2013)

21. Santos, H.N., Alves, P., Costa, I., Quintao Pereira, F.M.: Just-in-time value special-ization. In: CGO (2013)

22. Van Cutsem, T., Miller, M.S.: Traits.js: Robust object composition and high-integrity objects for ecmascript 5. PLASTIC (2011)

23. Xu, G., Arnold, M., Mitchell, N., Rountev, A., Sevitsky, G.: Go with the flow:profiling copies to find runtime bloat. In: PLDI (2009)

24. Xu, W., Huang, L., Fox, A., Patterson, D., Jordan, M.I.: Detecting large-scalesystem problems by mining console logs. In: SOSP (2009)

25. Yan, D., Xu, G., Rountev, A.: Uncovering performance problems in java applicationswith reference propagation profiling. In: ICSE (2012)

26. Yuan, D., Mai, H., Xiong, W., Tan, L., Zhou, Y., Pasupathy, S.: Sherlog: errordiagnosis by connecting clues from run-time logs. In: ASPLOS XV (2010)