Optimize your Node app by upgrading Node.js

In January, I published an article on RisingStack’s blog
. This article was an introduction to Node.js performance (and in V8 JavaScript Engine in general).

Now it is time for a follow-up article regarding the sources of performance bottlenecks in Node.js.

If a method contains a non-optimizable pattern, even if it is hot and stable, the JavaScript engine will not accept to optimize it.

The following table states for each pattern if it can be optimized on different versions of Node.js.

itemv0.12.18v4.0.0v6.0.0v7.0.0v7.7.4
Rest parametersN/A no no no no
access non-existent index of `arguments` no no no no no
`debugger` statement no no no no no
`for…in` loop whose key exists in outer scope no no no no no
leaks `arguments` via a closure no no no no no
object literal containing a getter no no no no no
object literal containing a setter no no no no no
overwrite named function argument and read `arguments` no no no no no
pass `arguments` to `call()` no no no no no
Unsupported phi use of arguments no no no no no
Unsupported phi use of const or let variable no no no no no
return `arguments` no no no no no
yield no no no no no
assign to `arguments` no no no no yes
calls `eval()` no no no no yes
`try…catch…finally` statement no no no yes yes
`try…finally` statement no no no yes yes
`try…catch` statement no no no yes yes
function is bound using `Function.prototype.bind()` no no yes yes yes
`for…in` loop using an array no yes yes yes yes
`switch` statement with over 128 cases no yes yes yes yes

As you can see, some patterns are not optimization killers in all Node.js versions.

This table has been built using a very handy repository by Colin Ihrig
. I forked it
to add some patterns and to change the output format. Some results (for instance, the patterns optimized by TurboFan) had to be computed using directly the --trace-opt --trace-deopt
flags.

In the rest of this article, we will go through the different patterns considered in this table.

Code patterns

For each pattern present in the table, we will give:

  • A code example
  • A short description of the language feature/pattern that explains (when possible) why it prevents V8 from optimizing it.

Rest parameters

Rest parameters
were introduced in the ECMAScript 2015 norm. This pattern is mainly a helper for the arguments
object. It introduces a lot of polymorphism in the signature of the function.

At the moment, V8 does not optimize methods that use this feature.

access non-existent index of `arguments`


The arguments
object

was introduced in the first version of ECMAScript. Using it adds polymorphism into the function’s signature.

As far as I know, only two uses of arguments
are not performance killers in V8.

`debugger` statement


The debugger

statement was introduced formally in the third version of the ECMAScript norm. Using it will potentially change the execution workflow of the script.

Optimizing a function containing such statement looks extremely complex. Anyway, there should not be any debugger
statement in production code.

`for…in` loop whose key exists in outer scope

for...in
loops gave a serious headache to the V8 team. Since they wrote on this topic, I think it is best to let them expose this topic: Fast For-In in V8 (on V8 Blog)

leaks `arguments` via a closure


The arguments
object

was introduced in the first version of ECMAScript. Using it introduces polymorphism into the function’s signature.

The Bluebird wiki has a very nice paragraph
regarding arguments
leaking.

object literal containing a getter and object literal containing a setter

ECMAScript offers a powerful way to execute code when (read or write) access to an object’s property is made. However, it introduces a possible polymorphism in the typing of the members of an object.

overwrite named function argument and read `arguments`

Once again, this one is about the use of arguments
. V8 does not like when you mess with arguments.

pass `arguments` to `call()`

You might want to pass the arguments of a function from a method to another. In this case, you should never use Function.prototype.call
but
Function.prototype.apply

. Since apply
accepts an Array-like object as the second argument, you can pass the arguments
object directly to it: myFunction.apply(null, arguments)
.

Unsupported phi use of arguments and Unsupported phi use of const or let variable

Those two are still a bit obscure to me. You can find some documentation on them on the v8 bailout reasons repository
.

return `arguments`

This is what is called an arguments leak. It is impossible to make safe heuristics regarding the signature of such a function. Therefore, its optimization is extremely unlikely.

Generators
have been introduced in the ECMAScript 2016 norm. Those objects have an unusual behavior for V8 since these functions can be exited and re-entered.

At the moment, V8 does not optimize generators.

assign to `arguments`

Historically, this pattern was not optimized and, as often, this is due to the use of the arguments
object.

However, it seems that latest versions of the engine optimize it.

calls `eval()`

Historically, this pattern was not optimized.
eval

is a function that uses some dark magic. I basically execute in the current context any arbitrary JavaScript code passed as a string.

Despites its unstable nature, it seems that latest versions of the engine optimize it thanks to its new architecture: TurboFan.

`try…catch…finally` statement, `try…finally` statement and `try…catch` statement

This is probably the coolest everyday-life enhancement in V8. It is possible thanks to TurboFan which is the new optimizing compiler
.

function is bound using `Function.prototype.bind()`


Function.prototype.bind

has been introduced in the 5.1 version of ECMAScript. It is used to create a new function from another by binding a context ( this
and arguments) to another function.

Benedikt Meurer has written an article
on this topic; it is a short and high-quality reading.

`for…in` loop using an array

As stated before, for...in
loops have been an issue for the V8 team. Once again, I advise reading their blog post
on this topic.

`switch` statement with over 128 cases

The real code is available in another gist
.

Historically, bloated
switch

statements were not optimized. But the V8 team removed this constraint a while ago.

Conclusion

Knowing how optimization works in the V8 JavaScript Engine allows you to benefit from the full power of this great engine. All the principles presented in that article are valid for Google Chrome Web Browser as well.

What about other JS engines?

I am currently going through the documentation of ChakraCore Node.js
version. This will probably result in another article on the topic.

Regarding other engines, I will wait to see the results of the VM Working Group
and I will try to cover as many Node.js versions as possible.

A few last words

Please let me know if you have any comments or remarks regarding this article. I am easily reachable on Twitter
.

Also, don’t hesitate to give
Sqreen

a try to detect users performing attacks in your app and block targeted attacks. ��

稿源:Sqreen Blog (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合技术 » Optimize your Node app by upgrading Node.js

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录

登录

忘记密码 ?

切换登录

注册