The translator presses: I first translation foreign language, the speech unavoidably some obscure, but as far as possible expressed the author's original intention, does not have the excessive polishing, welcome criticizes the correction. Another text is long, the information is big, may be difficult to digest, welcome the message to discuss the detail question. This article focuses on the performance optimization of V8, some content does not apply to all JS engines. Finally, reprint please indicate the source:)
Many JavaScript engines, such as Google's V8 engine (used by chrome and node), are designed specifically for large JavaScript applications that need to be executed quickly. If you are a developer and care about memory usage and page performance, you should know how the JavaScript engine works in the user's browser. Whether it's V8,spidermonkey (Firefox) Carakan (Opera), Chakra (IE) or other engines, doing so can help you better optimize your application . This is not to say that you should specifically optimize for a particular browser or engine, don't do that.
There are some common pitfalls in writing high-performance code, and in this article we will show some of the more validated and better ways to write code.
If you don't have a deep understanding of the JS engine, it's no problem to develop a large web application, just like a driver who has seen the hood without looking at the engine inside the hood. Since Chrome is my browser's first choice, talk about its JavaScript engine. The V8 is composed of the following core components:
Manual elimination of object references is not necessary in most cases. Everything will work very well by simply placing variables where they are needed (ideally, as far as possible, as local scopes, that is, in the function they are using rather than the outer layer of the function).
The garbage collector attempts to reclaim memory. Photo Source: Valtteri Mäki.
In JavaScript, it is not possible to enforce garbage collection. You should not do this because the garbage collection process is controlled by the runtime and it knows what is the best time to clean up.
Misunderstanding of "eliminating references"
There are a lot of discussions on JavaScript memory recycling on the web. The Delete keyword, although it can be used to delete the attributes (key) in the object (map), some developers think it can be used to force "eliminate references." It is recommended to avoid using delete as much as possible, in the following exampledelete o.x 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x:1};
Delete o.x; True
o.x;//undefined
You will easily find references to delete in the popular JS library--It's language-purposeful. The thing to note here is to avoid modifying the structure of the hot object at run time. The JavaScript engine can detect this "hot" object and try to optimize it. If the object's structure does not change significantly in its lifecycle, the engine will be more likely to optimize the object, and the delete operation will actually trigger this larger structural change, which is not conducive to engine optimization.
There are also misconceptions about how NULL works. Setting an object reference to null does not change the object to "empty", but it simply sets its reference to NULL. Using o.x= null is better than using delete, but it may not be necessary.
var o = {x:1};
o = null;
O Null
o.x//TypeError
If this reference is the last reference to the current object, the object will be garbage collected. If this reference is not the last reference of the current object, the object is accessible and will not be garbage collected.
Also note that global variables are not cleaned up by the garbage collector during the life cycle of the page. Regardless of how long the page is open, the JavaScript runtime variable in the global object scope persists.
var myglobalnamespace = {};
Global objects are cleaned only when you refresh the page, navigate to another page, close a label page, or exit the browser. The variables of the function scope are cleared when the scope is exceeded, that is, when the function is exited, there is no reference, and the variable is cleared.
Rule of thumb
In order for the garbage collector to collect as many objects as possible at the earliest possible time, do not hold objects that are no longer used . Here are a few things to remember:
- As mentioned earlier, using variables in the right range is a better choice to manually eliminate references. That is, a variable is only used in a function scope, do not declare it in the global scope. This means more clean and hassle-less code.
- Be sure to unbind event listeners that are no longer needed, especially those that are bound by Dom objects that are about to be destroyed.
- If the data used is cached locally, be sure to clean up the cache or use the aging mechanism to avoid large amounts of data that is not reused to be stored.
Function
Next, let's talk about functions. As we have said, garbage collection works by reclaiming memory blocks (objects) that are no longer accessed. To better illustrate this point, here are some examples.
function foo () {
var bar = new Largeobject ();
Bar.somecall ();
}
When Foo returns, the object that bar points to will be automatically recycled by the garbage collector because it has no existing references.
Compare:
function foo () {
var bar = new Largeobject ();
Bar.somecall ();
return bar;
}
Somewhere else
var b = foo ();
Now we have a reference to the bar object, so that the life cycle of the bar object lasts from the call of Foo until the caller specifies another variable B (or b out of range).
Closure (CLOSURES)
When you see a function that returns an intrinsic function, the internal function will gain access outside the scope, even after the external function executes. This is a basic closure--An expression of a variable that can be set in a particular context. For example:
function sum (x) {
function Sumit (y) {return
x + y;
};
return sumit;
}
Usage
var SumA = SUM (4);
var sumb = SumA (3);
Console.log (SUMB); Returns 7
The function object (SUMIT) generated in the sum invocation context cannot be reclaimed, it is referenced by global variables (SumA), and can be invoked through SumA (n).
Let's take a look at another example, where can we access variable largestr?
var a = function () {
var largestr = new Array (1000000). Join (' x ');
return function () {return
largestr;}
;
} ();
Yes, we can access largestr through a (), so it's not recycled. What's the bottom one?
var a = function () {
var smallstr = ' x ';
var largestr = new Array (1000000). Join (' x ');
return function (n) {return
smallstr;}
;
} ();
We can't visit largestr anymore, it's already a garbage collector candidate. "Translator Note: Because LARGESTR no longer has an external reference."
Timer
One of the worst memory leaks is in the loop, or in settimeout ()/setinterval (), but this is fairly common. Consider the following example:
var myobj = {
callmemaybe:function () {
var myref = this;
var val = settimeout (function () {
Console.log (' time is running out! ');
Myref.callmemaybe ();
}, 1000);
}
;
If we run Myobj.callmemaybe () to start the timer, we can see that the console prints "time is running out!" every second. If you then runmyObj =
null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
It is also worth remembering that references in Settimeout/setinterval calls (such as functions) will need to be executed and completed before they can be garbage collected.
Beware of performance traps
Never optimize the code until you really need it. Now you can often see benchmark tests that show that n is more optimized than M in V8, but testing in the module code or application will find that these optimizations actually have a much smaller effect than you expect.
Doing too much is better than doing nothing. Image source: Tim sheerman-chase.
For example, we want to create such a module:
- Requires a local data source containing a digital ID
- Draw the table that contains the data
- Add an event handler to toggle the cell's CSS class when the user clicks on any cell
This problem has several different factors, although it is easy to solve. How do we store data, how to efficiently draw tables and append into the DOM, and how to better handle table events?
The first (naïve) approach to these problems is to use the object to store the data and put it in an array, use jquery to traverse the data-drawing table and append into the DOM, and finally use the event to bind us to the desired click behavior.
Note: This is not what you should do.
var Modulea = function () {return
{
data:dataarrayobject,
init:function () {
this.addtable ();
This.addevents ();
},
addtable:function () {for
(var i = 0; i < rows; i++) {
$tr = $ (' <tr></ Tr> ');
for (var j = 0 J < This.data.length J + +) {
$tr. Append (' <td> ' + this.data[j][' id '] + ' </td> ');
$tr. Appendto ($tbody);}
},
addevents:function () {
$ (' table TD '). ON (' click ', Function () {
$ (this). Toggleclass (' active ');}
);
}
();
This code completes the task simply and efficiently.
But in this case, the data we're traversing is just a numeric attribute ID that should simply be stored in the array. Interestingly, using the DocumentFragment and local DOM methods directly is a better choice than using jquery (in this way) to generate tables, and of course, event proxies have a higher performance than binding each TD individually.
Note that while jquery uses documentfragment internally, in our case, the code calls append within the loop and these calls involve some other little knowledge, so the optimizations here are not very useful. Hopefully this will not be a pain point, but be sure to make a benchmark test to make sure your code is OK.
For our example, the above approach has resulted in (expected) performance improvements. The event agent is an improvement to simple bindings, and optional documentfragment also helps to push the load.
var moduled = function () {return
{
Data:dataarray,
init:function () {
this.addtable ();
This.addevents ();
},
addtable:function () {
var td, TR;
var frag = Document.createdocumentfragment ();
var frag2 = Document.createdocumentfragment ();
for (var i = 0; i < rows; i++) {
tr = document.createelement (' tr ');
for (var j = 0; J < This.data.length; J + +) {
td = Document.createelement (' TD ');
Td.appendchild (document.createTextNode (this.data[j));
Frag2.appendchild (TD);
Tr.appendchild (FRAG2);
Frag.appendchild (TR);
}
Tbody.appendchild (Frag);
},
addevents:function () {
$ (' table '). On (' Click ', ' TD ', function () {
$ (this). Toggleclass (' active ');}};}
();
Next look at other ways to improve performance. You might have read it somewhere. Using prototype mode is better than module mode, or you've heard that using the JS template framework is a good performance. Sometimes this is true, but they are actually used to make the code more readable. That's right, and precompiled! Let's see how it behaves in practice.
Moduleg = function () {};
ModuleG.prototype.data = DataArray;
ModuleG.prototype.init = function () {
this.addtable ();
This.addevents ();
};
moduleG.prototype.addTable = function () {
var template = _.template ($ (' #template '). text ());
var html = template ({' Data ': This.data});
$tbody. Append (HTML);
moduleG.prototype.addEvents = function () {
$ (' table '). On (' Click ', ' TD ', function () {
$ (this). Toggleclass (' Active ');
};
var modg = new Moduleg ();
It turns out that the performance boost in this case can be negligible. The choice of templates and prototypes does not really provide much more stuff. That is, performance is not why developers use them, but the readability, inheritance model, and maintainability of the code are the real reasons.
More complex issues include the efficient drawing of pictures on canvas and the manipulation of pixel data with or without an array of types.
Before you apply some methods to your own application, be sure to learn more about the benchmarking of these scenarios. Maybe someone remembers the shoot-off of JS templates and the extended version later. You need to be aware that benchmarking is not a virtual application that you can't see, but rather a test of the optimizations that you can bring to your actual code.
V8 Optimization Techniques
The optimization points of each V8 engine are described in detail outside the scope of this article, and there are certainly a number of techniques worth mentioning here. With these tips in mind, you'll be able to reduce the low performance code.
- Specific patterns can make V8 out of the dilemma of optimization, such as Try-catch. For more information on which functions can or cannot be optimized, you can use the –trace-opt file.js command in the V8 scripting tool D8.
- If you care about speed, try to make your function single, that is, ensure that variables (including attributes, arrays, function arguments) use only objects contained in the same hidden class. For instance, don't do this:
function add (x, y) {return
x+y;
}
Add (1, 2);
Add (' A ', ' B ');
Add (my_custom_object, undefined);
- Do not load uninitialized or deleted elements. If you do so, there will be no errors, but this will slow down.
- Do not make the function body too large, which makes optimization more difficult.
More content can go to see Daniel Clifford in Google I/O sharing breaking the JavaScript Speed Limit with V8. Optimizing for V8-a series is also well worth reading.
Object vs Array: Which should I use?
- If you want to store a bunch of numbers, or some objects of the same type, use an array.
- If you need semantically the attributes of a bunch of objects (different types), use an object and attribute. This is very efficient and very fast in memory.
- The elements of an integer index, whether stored in an array or an object, are much faster than the properties that traverse the object.
- Objects have complex properties: they can be created by setters, with different enumerations and writable features. In an array, there is no such customization, and there are only two states of existence and none. At the engine level, this allows for more optimization of the storage structure. In particular, when there are numbers in an array, such as when you need a container, you do not have to define a class with the X,y,z attribute, but only an array.
One of the main differences between objects and arrays in JavaScript is the Magic length property of arrays. If you maintain this property yourself, the speed of objects and arrays in V8 is just as fast.
Tips for working with objects
- Use a constructor to create the object. This ensures that all objects it creates have the same hidden classes and helps avoid changing these classes. As an additional benefit, it is also slightly faster than object.create ()
- In your application, there is no limit to the use of different types of objects and their complexity (within a reasonable range: Long prototype chains are often harmful, rendering only a very small number of objects faster than larger objects). For "hot" objects, try to keep a short prototype chain, and less attributes.
Object cloning
For application developers, object cloning is a common problem. Although benchmark tests can prove that V8 has handled the problem well, be careful. Copying big things is usually slower--don't do that. For in JS.. The in loop is especially bad because it has a demon-like specification and it may never be faster than any object in either engine.
Use an array or a custom copy constructor feature to explicitly copy each attribute when you are sure to copy the object on the key-capable code path. This is probably the quickest way:
function Clone (original) {
this.foo = Original.foo;
This.bar = Original.bar;
}
var copy = new Clone (original);
Caching functions in module mode
Caching functions when using module mode can lead to performance improvements. See the following example, because it always creates a new copy of the member function, and the changes you see may be slower.
Also note that using this method is significantly better than relying on prototype mode (confirmed by Jsperf test).
Performance improvements when using module or prototype mode
This is a performance comparison test for a prototype model and a module pattern:
Prototypal Pattern Klass1 = function () {} Klass1.prototype.foo = function () {log (' foo ');
} Klass1.prototype.bar = function () {log (' bar ');
}//Module pattern Klass2 = function () {var foo = function () {log (' foo ');
}, bar = function () {log (' bar ');
}; return {foo:foo, Bar:bar}}//Module pattern with cached functions var foofunction = function () {log (
' foo ');
};
var barfunction = function () {log (' bar ');
}; KLASS3 = function () {return {foo:foofunction, bar:barfunction}}//Iteration Tests//Prototypal VA
R i = 1000, objs = [];
while (i--) {var o = new Klass1 () Objs.push (New Klass1 ());
O.bar;
O.foo;
}//Module pattern var i = 1000, objs = [];
while (i--) {var o = Klass2 () Objs.push (Klass2 ());
O.bar;
O.foo;
}//Module pattern with cached functions var i = 1000, objs = [];
while (i--) {var o = Klass3 () Objs.push (KLASS3 ());
O.bar;
O.foo; }//The TEST for full details
Tips for using arrays
Next, let's talk about array-related techniques. In general, do not delete an array element , which will make the array transition to a slower internal representation. When the index becomes sparse, V8 will move the element to a slower dictionary mode.
The literal amount of the array
The literal value of an array is very useful, and it can imply the size and type of the VM array. It is usually used in an array of small size.
Here V8 can and so you want a 4-element array containing numbers:
var a = [1, 2, 3, 4];
Don ' t does this:
a = [[]; V8 knows nothing about the "array for"
(var i = 1; I <= 4; i++) {
a.push (i);
}
Storage single type vs multiple types
It is never a good idea to have data in a mixed type (such as numbers, strings, undefined, true/false) in an array. For example, var arr = [1, "1", Undefined, True, "true"]
Performance tests for type inference
As we can see, the array of integers is the fastest.
sparse arrays and full arrays
When you use sparse arrays, be aware that the access elements will be much slower than the full array. Because V8 does not allocate an entire block of space to an array that uses only part of the space. Instead, it is managed in the dictionary, which saves space but spends the time of the visit.
sparse arrays and full arrays of tests
Pre-allocated space vs dynamic allocation
Do not allocate large arrays (such as elements larger than 64K) to their maximum size, but should be dynamically allocated. Before we test the performance of this article, keep in mind that this applies only to some JavaScript engines.
Photo Source: Per Olof Forsberg.
Understanding and improving the performance of your application is very useful, but it is also difficult. We recommend the following steps to address the pain point of performance:
- Measurement: Find a slow place in your application (approx. 45%)
- Understanding: Find out what the real problem is (about 45%)
- Fix It! (About 10%)
Some of the tools and techniques recommended below can help you.
Benchmarking (Benchmarking)
There are many ways to run the benchmark of JavaScript code fragments to test its performance--the general assumption is that the benchmark simply compares two timestamps. This pattern is indicated by the Jsperf team and is used in the Sunspider and Kraken base suites:
var totaltime,
start = new Date,
iterations = 1000;
while (iterations--) {
//Code snippet goes here
}
//totaltime→the number of milliseconds taken
//to E Xecute the code Snippet 1000 times
totaltime = new Date-start;
Here, the code to test is placed in a loop and runs a set number of times (for example, 6 times). After that, the start date subtracts the end date, which results in the time it takes to perform the operation in the loop.
However, this benchmark test does things too simply, especially if you want to run benchmarks in multiple browsers and environments. The garbage collector itself has a certain effect on the result. Even if you use a solution like window.performance, you have to take these flaws into account.
Whether you run the code for the base section or not, write a test suite or coded base library, the JavaScript benchmark is actually more than you think. For a more detailed guide benchmark, I strongly recommend that you read the JavaScript benchmark test provided by Mathias Bynens and John-david Dalton.
Analysis (PROFILING)
The Chrome developer tool is a good support for JavaScript analytics. You can use this feature to detect which functions are taking up most of the time, so you can optimize them. This is important, even a small change in code can have a significant impact on overall performance.
Memory statistics in the Chrome Developer tool
Memory counts the private memory usage that our team cares about, the size of the JavaScript heap, the number of DOM nodes, the storage cleanup, the event listener counters, and what the garbage collector is trying to reclaim. Read Loreena Lee's "3 snapshots" technique. The point of this technique is to record some behavior in your application, Force garbage collection, check whether the number of DOM nodes is restored to the expected baseline, and then analyze the three-heap snapshots to determine if there is a memory leak.
Memory management for single page application
Memory management for single page applications, such as Angularjs,backbone,ember, is very important and they will almost never refresh the page. This means that a memory leak can be quite obvious. A single page application on a mobile terminal is full of traps because the device has limited memory and is running applications such as email clients or social networks for a long time. The greater the capacity, the heavier the responsibility.
There are many ways to solve this problem. In backbone, be sure to use Dispose () to handle old views and references (currently available in backbone). This function was recently added to remove the handler function added to the View ' event ' object and the model or collection event listener passed to the third parameter (callback context) of view. Dispose () is also invoked by the view's remove () to handle the primary cleanup when the element is removed. Other libraries such as Ember when the element is removed, the listener is cleaned up to avoid a memory leak.
Some sensible advice from Derick Bailey:
Rather than understanding how events and references work, it is better to follow the standard rules for managing memory in JavaScript. If you want to load data into a backbone collection of user objects, you need to empty the collection so that it no longer consumes memory, and that must be all references to the collection and the objects in the collection. Once the reference is made clear, the resource is reclaimed. This is the standard JavaScript garbage collection rule.
In this article, Derick covers a number of common memory flaws when using backbone.js, and how to troubleshoot these problems.
Felix Geisendörfer's tutorial on debugging memory leaks in node is also worth reading, especially as it forms part of a broader spa stack.
Reduced backflow (reflows)
When browsers recreate the elements in the document, they need to recalculate their position and geometry, which we call reflux. Backflow can block the user's actions in the browser, so it is helpful to understand the timing of the upgrade.
Reflow Time Chart
You should trigger the reflow or redraw in batches, but use these methods sparingly. It is also important not to handle the DOM as much as possible. You can use DocumentFragment, a lightweight document object. You can use it as a way to extract part of the document tree, or to create a new document "Fragment". Instead of constantly adding DOM nodes, use the document fragment only once to perform a DOM insert operation to avoid excessive reflow.
For example, we write a function to add 20 div to an element. If you simply append one div to the element at a time, this triggers a 20-time return.
function Adddivs (Element) {
var div;
for (var i = 0; i < i + +) {
div = document.createelement (' div ');
div.innerhtml = ' heya! ';
Element.appendchild (div);
}
To solve this problem, you can use DocumentFragment instead, we can add a new div to the inside each time. Adding DocumentFragment to the DOM when finished will only trigger a reflow.
function Adddivs (Element) {
var div;
Creates a new empty documentfragment.
var fragment = Document.createdocumentfragment ();
for (var i = 0; i < i + +) {
div = document.createelement (' a ');
div.innerhtml = ' heya! ';
Fragment.appendchild (div);
}
Element.appendchild (fragment);
}
You can see make the Web faster,javascript Memory optimization and finding Memory leaks.
JS Memory Leak Detector
To help discover JavaScript memory leaks, Google's developers (Marja Hölttä and Jochen Eisinger) developed a tool that was used in conjunction with the Chrome Developer tool to retrieve a snapshot of the heap and detect what object caused the memory leak.
A JavaScript memory leak detection Tool
There is a complete article about how to use this tool, suggest yourself to the memory Leak detector project page to see.
If you want to know why such tools have not been integrated into our development tools for two reasons. It was originally in the closure library to help us capture some specific memory scenarios, which are more suitable as an external tool.
V8 optimization debugging and garbage collection logo bit
Chrome support directly by passing some flags to the V8 for more detailed engine tuning output results. For example, this can track the optimization of V8:
"/Applications/Google chrome/google Chrome"--js-flags= "--trace-opt--trace-deopt"
Windows users can run Chrome.exe–js-flags= "–trace-opt–trace-deopt" like this
The following V8 flags are available when you are developing an application.
- trace-opt--records the name of the optimization function and shows skipped code because the optimizer does not know how to optimize it.
- trace-deopt--records the code that will be "optimized" at run time.
- The trace-gc--records each garbage collection.
V8 's processing script uses a * (asterisk) to identify the optimized function, with a ~ (wave number) representing the function that is not optimized.
If you are interested in learning more about V8 's logo and how the V8 interior works, it is highly recommended to read the excellent post on V8 internals of Vyacheslav Egorov.
High-resolution time and navigation TIMING APIs
High-precision Time (HRT) is a millisecond-level, high-precision time interface that provides an effect that is not subject to system time and user tuning, and can be used as a more accurate metric than new Date and Date.now (). This is a great help for us to write benchmark tests.