简体   繁体   English

Array.map() 的箭头函数,.bind 还是 argThis? 垃圾收集 + 性能

[英]Arrow functions, .bind or argThis for Array.map()? garbage collection + performance

I'm working on a project that does billions of requests per day, and there's a big concern about performance and memory usage.我正在从事一个每天处理数十亿个请求的项目,并且非常关注性能和内存使用情况。

Yesterday I implemented some changes in a code that executes lots and lots of times per minute, and I used arrow functions for some mapping, but the team asked me to always use argsThis when using .map, and I didn't get why, so I did a lot of benchmarks, and it shows the opposite.昨天我在每分钟执行很多次的代码中实现了一些更改,并且我使用了箭头函数进行了一些映射,但是团队要求我在使用 .map 时总是使用 argsThis,我不明白为什么,所以我做了很多基准测试,结果恰恰相反。

Benchmark (Bind vs Arrow Function vs ArgsThis)基准测试(绑定 vs 箭头函数 vs ArgsThis)

Their argument is that garbage collection is way worst when using Arrow Functions, and these cases are not showing a real scenario because it has a very shallow context.他们的论点是垃圾收集在使用箭头函数时是最糟糕的,并且这些情况并未显示真实场景,因为它具有非常浅的上下文。

在此处输入图片说明

The benchmarks shows that arrow function are much faster, is there something I'm missing to consider?基准测试表明箭头函数要快得多,我有什么需要考虑的吗?
Thanks!谢谢!

EDIT:编辑:

The question is for cases we need variables from the upper context, for example:问题是对于我们需要来自上层上下文的变量的情况,例如:

function doSomething() {
    const someUpperContextValue = 5;
    [1, 2, 3].map((currentValue) => calculateValues(someOutterContextValue, currentValue));
}

Disclaimer: just know that your less-than-real-world benchmarks likely don't show the full picture, but I won't get into that here.免责声明:只要知道您的低于现实世界的基准可能不会显示全貌,但我不会在这里讨论。

We can take a very elementary look at how JavaScript engines call functions.我们可以非常基本地了解 JavaScript 引擎如何调用函数。 Please note that I am not an expert on how JS runtimes work, so please correct me where I am wrong or incomplete.请注意,我不是 JS 运行时如何工作的专家,所以请纠正我的错误或不完整的地方。

Whenever a function is executed, a "call scope" is created and added to the stack.每当执行函数时,都会创建一个“调用范围”并将其添加到堆栈中。 For normal/classical functions (non-arrow functions), the engine creates a new context which has its own "closure" and variable scope.对于普通/经典函数(非箭头函数),引擎创建一个新的上下文,它有自己的“闭包”和变量范围。 Within this context are some implicitly created variables such as this and arguments which the engine has put there.在此上下文中是一些隐式创建的变量,例如引擎放置在那里的thisarguments

function foo() {
  const self = this;
  function bar(...args) {
    console.log(this === self); //-> false
    console.log(arguments); //-> { length: 1, 0: 'barg' }
    console.log(args); //-> [ 'barg' ]
  }
  bar('barg');
  console.log(arguments); //-> { length: 1, 0: 'farg' }
} 
foo('farg');

Arrow functions work very much like regular functions but without the additional closure and extra variables.箭头函数的工作方式与常规函数非常相似,但没有额外的闭包和额外的变量。 Pay close attention to the difference in log results:密切关注日志结果的差异:

function foo() {
  const self = this;
  const bar = (...args) => {
    console.log(this === self); //-> true
    console.log(arguments); //-> { length: 1, 0: 'farg' }
    console.log(args); //-> [ 'barg' ]
  }
  bar('barg');
  console.log(arguments); //-> { length: 1, 0: 'farg' }
} 
foo('farg');

Armed with this very topical... almost superficial... knowledge, you can see that the engine is doing "less work" when creating arrow functions.有了这个非常热门的......几乎是肤浅的......知识,你可以看到引擎在创建箭头函数时做了“更少的工作”。 Reason would stand that arrow functions are inherently faster because of this.有理由认为箭头函数本质上更快。 Furthermore, I don't believe arrow functions introduce any more potential for memory leaks or garbage collection than regular functions ( helpful link ).此外,我认为箭头函数不会比常规函数( 有用的链接)引入更多潜在的内存泄漏或垃圾收集。

Edit:编辑:

It's worth mentioning that every time you declare a function, you're defining a new variable which is taking space in memory.值得一提的是,每次声明一个函数时,都会定义一个占用内存空间的新变量。 The JavaScript engine must also preserve the "context" for each function such that variables defined in "parent" scopes are still available for the function when it executes. JavaScript 引擎还必须为每个函数保留“上下文”,以便在“父”作用域中定义的变量在函数执行时仍然可用。 For example, any time you call foo above, a new bar variable is created in memory with access to the full context of its parent "foo" call scope.例如,任何时候在上面调用foo ,都会在内存中创建一个新的bar变量,可以访问其父“foo”调用范围的完整上下文。 JS engines are good about figuring out when a context is no longer needed and will clean it up during garbage collection, but know that even garbage collection can be slow if there's a lot of trash. JS 引擎很擅长弄清楚何时不再需要上下文并在垃圾收集期间将其清理干净,但要知道如果垃圾很多,即使垃圾收集也会很慢。

Also, engines have an optimization layer (see Turbofan ) which are really smart and constantly evolving.此外,引擎有一个优化层(参见Turbofan ),它非常智能且不断发展。 To illustrate this, consider the example you provided:为了说明这一点,请考虑您提供的示例:

function doSomething() {
    const someUpperContextValue = 5;
    [1, 2, 3].map((currentValue) => calculateValues(someOutterContextValue, currentValue));
}

Without knowing much about the internals of JS engines, I could see engines optimizing the doSomething function because the someUpperContextValue variable has a static value of 5 .在不太了解 JS 引擎内部的情况下,我可以看到引擎优化doSomething函数,因为someUpperContextValue变量的静态值为5 If you change that value to something like Math.random() , now the engine doesn't know what the value will be and cannot optimize.如果将该值更改为Math.random()类的值,现在引擎不知道该值将是什么并且无法优化。 For these reasons, many people will tell you you're wasting your time by asking "which is faster" because you never know when a small innocuous change completely kills your performance.由于这些原因,很多人会告诉你问“哪个更快”是在浪费时间,因为你永远不知道什么时候一个小的无害变化会完全扼杀你的表现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM