JS performance optimization in V8

Note: This topic is in the area of performance optimization.Copy the code

Performance issues are becoming a hot topic on the front end, because as projects get bigger, they manifest themselves. In order to improve the user experience and reduce load times, engineers went to great lengths to optimize the details. Browser, Webpack and network protocol optimization will not be mentioned in these chapters. If you want to learn how to optimize the performance of these parts, please follow me and I will update you next time. In this chapter we’ll learn how to get V8 to tune our code, and in the next installment we’ll learn the rest of the nitty gritty of performance tuning, which is a very fragmented area. Before we learn how to optimize performance, let’s take a look at how to test for performance problems.

Test performance tool

Chrome already provides a large and comprehensive performance testing toolAudits

When we click on the Audits, we see the following interface



From this interface, we can select the functionality we want to test and then click on Run audits, and the tools Run automatically to help us test the problem and give us a full audit.

Wait on this screen.





The figure above is a report given to Baidu home page after testing performance. It can be seen that the report gives scores for performance, experience and SEO respectively, and each indicator has a detailed evaluation.



At the end of the evaluation, the tool also provided some suggestions for us to improve our score on this metric.



We just need to optimize the performance one by one according to the suggestions.

In addition toAuditsIn addition to the tools, there is anotherPerformanceTools are also available to us.



Reports from the Performance tool

In this graph, we can see in detail what the browser is doing in each time period, and which process is consuming the most time, so that we can understand the performance bottleneck in more detail.

JS performance optimization

Whether JS is a compiled or interpreted language is not fixed. First, JS needs an engine to run, either in a browser or in Node, which is a feature of interpreted languages. However, with V8, TurboFan compilers are introduced, which are optimized in specific situations to compile Code into more efficient Machine Code. Of course, this compiler is not necessary for JS, but only to improve Code execution performance. So JS in general tends to be an interpreted language.

This section will focus on Chrome’s V8 engine.

In this process, JS Code is first parsed into an Abstract syntax tree (AST) and then converted into Bytecode or Machine Code by an interpreter or compiler.



As you can see from the figure above, JS is parsed to AST first, which is actually a bit slower. The more code, the longer the parsing process takes, which is one of the reasons we need to compress the code. Another way to reduce parsing time is pre-parsing, which applies to functions that haven’t been executed, which we’ll talk about later.



How fast mobile phones parse JS code in 2016

One thing to note here is that for functions, you should avoid declaring nested functions (classes are also functions) as much as possible, because this will cause the function to be parsed repeatedly.

function test1() {
  // will be parsed repeatedly
  function test2() {}}Copy the code

thenIgnitionResponsible for converting AST to Bytecode,TurboFanResponsible for compiling optimized Machine Code, and Machine Code is superior to Bytecode in execution efficiency.



So that begs the question,When will Code compile to Machine Code?

JS is a doorDynamic typeAnd a whole bunch of rules. Simple addition code takes several rules into account internally, such as adding numbers, adding strings, adding objects and strings, and so on. Such a situation is bound to lead to the internal need to increase a lot of judgment logic, reduce operation efficiency.

function test(x) {
  return x + x
}
test(1)
test(2)
test(3)
test(4)
Copy the code

For the above Code, if a function is called multiple times and the argument keeps passing in type number, V8 will assume that the Code can be compiled into Machine Code because you have fixed the type and do not need to perform much judgment logic.

However, Machine Code is DeOptimized as Bytecode as soon as the type of argument we pass changes, and there is a performance penalty. So if we want our Code to compile to Machine Code more often and DeOptimized less frequently, we should make sure that the incoming type is as consistent as possible.

So you might be wondering how much of an improvement there is before and after optimization, so let’s actually test that out.

const { performance, PerformanceObserver } = require('perf_hooks')

function test(x) {
  return x + x
}
// PerformanceObserver is in node 10
// Node versions prior to this can use the PERFORMANCE API directly
const obs = new PerformanceObserver((list, observer) = > {
  console.log(list.getEntries())
  observer.disconnect()
})
obs.observe({ entryTypes: ['measure'].buffered: true })

performance.mark('start')

let number = 10000000
// do not optimize the code
%NeverOptimizeFunction(test)

while (number--) {
  test(1)
}

performance.mark('end')
performance.measure('test'.'start'.'end')
Copy the code

In the above code we use the Performance API, which is very useful for performance testing. Not only can it be used to measure code execution time, but it can also be used to measure time consumption in various network connections, etc., and the API can also be used in browsers.



The huge gap between optimized and unoptimized code

As you can see from the figure above, the optimized code takes only 9ms to execute, while the unoptimized code takes twenty times as long to execute, approaching 200ms. In this case, I’m sure you’ve seen how well V8’s performance is optimized, and the engine’s underlayer can help us optimize the code automatically, as long as we write code according to certain rules.

In addition, the compiler also has a Lazy-Compile operation, which preparses the function when it is not executed and does not Compile it until the code has been executed. For the above code, the test function needs to be parsed once and then parsed and compiled when it is called. But in cases where the function is called immediately, the process of pre-parsing is actually redundant, so what can be done to prevent the code from being pre-parsed?

It’s really easy, we just need to put parentheses around the function

(function test(obj) {
  return x + x
})
Copy the code

However, it is not possible to wrap parentheses around all functions for performance optimization, and not all functions need to do so. We can do this with optimize-js, a library that will analyze the usage of some functions and then add parentheses to the required functions, but of course the library hasn’t been maintained for a long time, so if you’re going to use it, you’ll have to test it.

summary

  • You can use the Audit tool to obtain performance reports for multiple metrics on your site
  • You can use the Performance tool to learn about Performance bottlenecks of a website
  • Time can be measured using the Performance API
  • To reduce compilation time, we can reduce the size of code files or write fewer nested functions
  • In order for V8 to optimize the code, we should try to ensure that the types of the parameters passed in are as consistent as possible. This leads us to wonder if this is one of the benefits of working with TypeScript