[Meting] [Music server=“tencent” id=“000AwJtd3Wp27b” type=“song”/] [/Meting]
我在 medium 上看到一篇 3 JavaScript Performance Mistakes You Should Stop Doing 文章(点击阅读全文可以查看原文,需要科学上网),大概意思就是说有 3 个 JavaScript 性能错误,你不应该再去写了。很多“歪果仁”也是一看到这个标题就开始**“喷”**作者了,下文会详细说。我先介绍下这篇文章的主要内容
文章主要内容:
当 ES5 发布的时候,JavaScript 引入了很多新的数组函数。其中包括 forEach,reduce,map,filter - 它们让我们感觉语言在不断增长,功能越来越强大,编写代码变得更加有趣和流畅,结果更易于阅读和理解。
大约在同一时间,一个新的环境—Node.js,它使我们能够从前端到后端平稳过渡,同时真正重新定义完整的全栈开发。
所以作者就测试了一下新提供的这些方法是否会影响我们程序的性能。他在 macOS 上对Node.js v10.11.0 和 Chrome 浏览器执行了以下测试。
1. 循环数组
他想到的第一次很常见的场景,就是计算一下 10k 项的总和。然后比较了使用 for,for of,while,forEach 和 reduce 的随机 10k 项的总和。运行测试 10,000 次返回以下结果:
For Loop, average loop time: ~10 microseconds
For-Of, average loop time: ~110 microseconds
ForEach, average loop time: ~77 microseconds
While, average loop time: ~11 microseconds
Reduce, average loop time: ~113 microseconds
在谷歌搜索如何对数组求和时,reduce 是最好的解决方案,但它是最慢的。即使是最新的(ES6)也提供了较差的性能。事实证明,老的 for 循环提供了迄今为止最好的性能 - 超过 10 倍以上!
最新推荐的解决方案如何使 JavaScript 变得如此之慢?造成这种痛苦的原因有两个主要原因:reduce 和 forEach 需要执行一个回调函数,这个函数被递归调用并使堆栈膨胀,以及对执行代码进行的附加操作和验证(在此描述 https://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21)。
2. 复制数组
虽然这听起来不那么有趣,但这是不可变函数的支柱,它在生成输出时不会修改输入。
此处的性能测试结果再次显示了同样有趣的趋势 - 当重复 10k 随机项的 10k 数组时,使用旧的传统解决方案更快。同样最新的 ES6 扩展操作符 [... arr]
和来自 Array.from(arr)
的数组加上 ES5 的 map arr.map(x => x)
不如老的 slice arr.slice()
和 concat [] .concat(arr)
。
Duplicate using Slice, average: ~367 microseconds
Duplicate using Map, average: ~469 microseconds
Duplicate using Spread, average: ~512 microseconds
Duplicate using Conct, average: ~366 microseconds
Duplicate using Array From, average: ~1,436 microseconds
Duplicate manually, average: ~412 microseconds
3. 迭代对象
另一种常见的情况是迭代对象,当我们尝试遍历 JSON 和对象时,这是必要的,而不是寻找特定的键值。同样有老 的解决方案,如 for-in for(let key in obj)
,或者后来的 Object.keys(obj)
(在 es6 中显示)和 Object.entries(obj)
(来自ES8)它返回 keys 和 vaues。
使用上述方法对 10k 个对象迭代进行性能分析,每个迭代包含 1,000 个随机 key 和value,得到以下结论。
Object iterate For-In, average: ~240 microseconds
Object iterate Keys For Each, average: ~294 microseconds
Object iterate Entries For-Of, average: ~535 microseconds
原因是在后两个解决方案中创建了可枚举值数组,而不是在没有 keys 数组的情况下直接遍历对象。但最终结果仍然引起关注。
最后 我的结论很清楚 - 如果快速性能对您的应用程序至关重要,或者您的服务器需要处理一些负载 - 使用最酷,更易读,更感觉的解决方案将会对您的应用程序性能产生重大影响 - 最多可以达到慢 10 倍!
下一次,在盲目采用最新趋势之前,确保它们也符合您的要求 - 对于小型应用程序,快速编写和更易读的代码是完美的 - 但对于压力大的服务器和大型客户端应用程序,这可能不是最好的做法。
评论吃瓜
评论 1
一个英文名叫 Christian Gitter 评论说:
不同意。谁在乎微秒?
如果您正在开发一个高性能的超级关键服务器应用程序,那么您要么首先不使用 JavaScript,要么您将成为一名经验丰富的开发人员,他知道自己在做什么以及谁不仅仅取得他的第一个结果。 “如何将数组相加”,Google 搜索结果并将其作为目标。
我们假设你有一个你注意到的服务很慢。你有两个选择。选项 1 占用了团队中的一个或几个开发人员,让他们花一些时间来优化代码以提高速度。选项 2 正在投入一些资金来扩展您的硬件。我说几乎总是选择 2。
在短期内,让您的开发人员进行优化工作可能比扩展服务器所需的成本更高。长期成本甚至更高,因为您将不得不继续进行这种优化,并且您将失去代码可读性,因此新开发人员需要更长时间来确定代码的作用。
这是你几乎应该做的事情:
- 循环数组=>
[].forEach(...)
- 复制数组=>
const newArray = [... oldArray]
- 迭代对象
…如果你只需要 values =>
Object.values(obj).forEach()
…或者如果你想要 keys =>Object.entries(obj).forEach()
…或者只是 keys =>Object.keys(obj).forEach()
我想你明白我的意思。
作者回复:
“谁在乎微秒?” - 好吧,在我工作的地方,我们每天处理大约550亿个事件,这意味着每秒大约700k个事件,当我们尝试在这种环境中运行节点时…你知道其余的事情。
Stephen Young 回复作者
好吧,让我们对每秒 700k 事件进行数学计算。让我们说,为了论证,20% 的事件(每秒 140k)正在进行一些繁重的工作并循环超过一万件事情。现在,假设您将这些循环从 forEach
优化到 for
循环。您的“基准”可为此更改节省 67 微秒。700k * 0.20 * 67 等于 938 万微秒。这归结为节省了惊人的 9.38 秒。这些秒不是线性的,因为我假设您没有在单个 JavaScript 线程上使用单个服务器消耗 700k 事件。在那种规模上,你并行运行多个线程。所以在这个非常慷慨的例子中,你每秒循环 10k 项、 140k次…你最终可能节省不到一秒钟。
评论 2
对于真正的应用程序性能,整个博客文章遗憾地是非常糟糕的建议。
在优化性能时应该做的第一件事是找到应用程序的实际瓶颈。否则,花费时间来优化对实际执行时间没有实际影响的代码。我是一名软件架构师,我最喜欢的一件事就是让代码快速发展。根据我的经验,主要的瓶颈主要是算法复杂性差。除此之外,算法中经常出现错误,并且在实现中存在许多奇怪之处。所以请使用 https://clinicjs.org/ 等工具。这有助于找到应该优化的代码。
这篇文章中提到的优化是微优化,降低了代码的可读性,因此代码需要更多的时间来阅读和理解,这导致优化热代码路径的时间更短。性能也只是当前版本的快照,并且由于新的引擎优化,相同的代码在下一版本中可能表现得非常不同。
如果内置函数确实比不同的实现慢得多(由于 V8 团队很厉害,这种情况不再那么常见),请向 V8 团队报告,以便他们可以进一步优化这些部分。还要注意,由于底层引擎优化(死代码消除等),基准测试本身可能不会按预期运行。
个人看法
首先发布个人看法,我觉得这篇文章还是很有价值的,很有趣的,它带给我们的价值不是说这些比较的数字比较有价值,而是另外的两点:
- 让我们要注意,google 出来的第一个答案不一定是好的答案。
- ES 新发布的特性不一定是最好的解决方案,我们需要引起注意。
- 当发现性能瓶颈的时候这也许是个方案(但是依我看来大多数用不上)
另外我观看评论得出以下结论:
-
过早优化是万恶之源,代码的可读性比性能更重要,因为代码是写来给人看的,不是给机器看的。这点让我想到前几天在掘金里面争论 FP(函数式编程) 和 OOP(面向对象编程)哪个好,或者说该用哪个?我的观点就是看自己的团队,如果你的团队大部分人都喜欢 FP,都熟悉 FP,大家都看得懂 FP,那么就可以用 FP,比如 Facebook 的 React 团队,他们很多都热衷于 FP,那么都是用 FP 有何不可呢?因为 FP 的抽象程度更高,所以对开发者要求相对高一点,所以对于不习惯 FP 的团队,还是老老实实的 OOP 吧。我这里没说 FP 就比 OOP 更好,只是想表达看个人爱好的团队爱好。
-
对于作者文中提到的这种微优化,在绝大部分情况下是没必要的,性能瓶颈往往可以通过优化算法来解决,算法解决不了可以通过增加服务器来解决。(所以这个观点我跟大部分的评论者相同)
-
“喷”也要有理有据,不赞同别人的观点,你可以举一些反例来证明,而不是当颠覆自己的认知的时候,当键盘侠,评论什么,垃圾文章,傻逼,v8 团队写的不好,你写的得好之类的话。另外我们要明白,有时候作者起的文章标题有点偏激,或者极端,他只是想引起你的重视,因为他觉得这点很重要,很可能是颠覆你以前的认知的事儿。