2025年webflux太难用了(webflux性能提高多少)

webflux太难用了(webflux性能提高多少)现在你很可能会遇到不止一个响应迟钝的 app 或加载缓慢的页面 已经是 2017 年了 我们当然希望一切变的很快 但我们仍然会体验到恼人的延时 怎么会这样呢 难道我们的网络连接不是逐年变快的么 我们的浏览器性能不是也变的更好 我们将在下文中讨论这些 事实上 浏览器和引擎越来越快 新特性也在不停的增加 一些过时的特性也在被废弃 网站和 app 也是如此 同时 它们也更大 更重了

大家好,我是讯享网,很高兴认识大家。



现在你很可能会遇到不止一个响应迟钝的 app 或加载缓慢的页面。已经是 2017 年了,我们当然希望一切变的很快,但我们仍然会体验到恼人的延时。怎么会这样呢?难道我们的网络连接不是逐年变快的么?我们的浏览器性能不是也变的更好?我们将在下文中讨论这些。

事实上,浏览器和引擎越来越快,新特性也在不停的增加,一些过时的特性也在被废弃。网站和 app 也是如此。同时,它们也更大、更重了,因此即使浏览器和硬件越来越好,我们也需要考虑性能 – 至少在某种程度上。我们来看看如何找出常见的性能陷阱,来改善网站和 app 的性能,但在此之前,我们先来看一下概览。

关于流水线(pipeline)我可以写一本书,但本文中我还是想关注有助于优化过程的关键点。我会阐述一些会极大影响性能的常见错误。为了简洁,我不会讨论 parsing、AST、机器码生成、GC(垃圾收集)、反馈收集、OSR(on-stack replacement) – 别担心,我会在未来的文章中解释它们。

旧版本

旧版本使用的基准编译器(baseline compiler)和优化编译器 Crankshaft,已经在 Chrome M59 中被废弃。

基准编译器并不会进行任何优化,它仅仅是快速编译代码,然后使其被执行。需要注意的是,生成优化代码严重依赖于假设,它反过来又需要假设类型反馈,因此需要首先执行基准编译器。

一旦某个函数被频繁执行(hot,通常引擎认为它值得优化),Crankshaft 就发挥(优化)作用了。它生成的代码性能非常好,接近于 Java。这种优化方式是业内第一,它带来了巨大的性能提升。因此 JS 才能有较好的性能,前端开发者也能够用它来创建复杂的 web 应用。

新版本

随着 web 的发展,新的框架诞生,规范也在更新升级,在 Crankshaft 基础上扩展变得非常困难。有的代码不会被 Crankshaft 优化,比如操作 arguments 对象的某些方法(安全的方式有 unmonkey-patched Function.prototype.apply、length属性、未越界的下标),try-catch 语句和其它。幸运的是,新的架构 Ignition 和 TurboFan 可以解决其中一些性能瓶颈。现在,有一些模式可以得到更好的优化。如前文所述,优化也是有成本的,需要耗费一些资源(在低端的移动设备上资源可能很有限)。但在多数情况下,你还是希望你的函数能够得到优化。

引入 TurboFan 的原因有:

  • 提供统一的代码生成架构
  • 减少 V8 的移植/维护成本
  • 去除性能陷阱
  • 新特性实验更容易 (i.e. changes to load/store ICs, bootstrapping an interpreter)

当然,前提是不牺牲性能。生成字节码相对很快,但解释字节码可能比执行优化后的代码慢 100 倍。它显然取决于编译器的复杂度。基准编译器的目的从来就不是生成很快的代码,但将执行时间考虑在内的话,它仍然比 Ignition 快(不是快很多,在某些场景下快 3-4 倍)。TurboFan 的目的是取代上一代的优化编译器 – Crankshaft。

不一定。

如果一个函数只会执行一两次,并不值得优化。但如果可能执行多次,值类型和对象结构固定的话,你就很可能需要考虑优化你的代码了。我们可能不会意识到规范中的一些异常。而引擎需要处理(这些异常),通常很难理解。举例:读取属性时,引擎需要考虑到各种边界情况,通常在真实场景下不会发生。为什么会这样呢?有时是为了向后兼容,有时是其它原因 – 每种情况都有不同。如果发现多余的操作,我们根本就不需要执行!优化引擎会发现这样的场景,尝试去除掉多余的操作。去除后的函数就称为 stub。

由于 JS 是动态类型语言,我们需要做很多假设。所以最好让属性保持单态 – 换句话说,应该只有一个路径。一旦假设不匹配,就会发生反优化(deopt),优化过的函数也就不再生效。这无疑是我们要避免的。每次优化都是或多或少需要耗费资源,再次优化时就需要考虑到之前的情况,以避免属性不是单态。只要不多于 4 条路径,它就会保持多态(polymorphic)。多于 4 条路径的话,称为 megamorphic。

只有传递了参数 , 才可以使用 

一般情况下,你不应该使用它们。在 V8 的源码(src/runtime)中可以找到它们的定义。所有会引起反优化的原因(bailout/deopt reasons):cs.chromium.org/chromium/sr…)

传递参数 ,可以查看你的函数是否被优化;传递 ,查看已优化的函数出现反优化的情况。

先来看一个非常简单的例子。

首先我们定义一个计算加法的函数 add,它接收 2 个加数,返回它们相加后的结果。很简单,对吧?继续看后面的代码:

如果运行的 V8 版本低于 5.9,必须显式传递 

运行上面的命令,会得到以下类似输出:

如你所见,这里有至少 3 种不同情况,我们的函数(add)出现反优化(deopt)。

如果将 lazy deopt 考虑在内,会发现更多,但我们还是关注 eager deopt。

顺便讲一句,此时这里有三种类型的反优化:eager、lazy、soft。

可能看起来有些难懂可怕,别担心,你很快就会明白的!

从第一个反优化开始:

原因是:“not a Smi”。如果已经听说过 Smi,你就可以直接跳过这一段了。

Smi 本质上就是小整数的缩写(small integer)。它与 V8 中其它对象有很多不同。在 V8 的源码中位于 objects.h: chromium.googlesource.com/v8/v8.git/+…

你会发现,Smi不是堆对象。

堆对象,指所有分配于堆上的变量的超类。我们(前端开发者)能够存取的变量本质上是 JSReceiver 的子类。

比如,我们经常用的数组(JSArray)和函数(JSFunction)就继承自这个类(JSReceiver)。

查找 Javascript schemes 标签的相关信息,你会发现 Smi 不同于它们。

在 64 位机器上,Smi 是 32 位有符号整数;而在 32 位机器上,它是 31 位有符号整数。

如果传给它这个这个范围之外的值,这个函数就会发生反优化。

比如:

因为 231 大于 231 - 1,所以会发生反优化。

当然,如果传给它数字之外的值,比如字符串、数组或其它类型的值,也会发生反优化。例如:

接下来看第二个反优化的情况。

与上面的情况类似,唯一的区别是它检查的是第二个参数 。

好,来看最后一个情况:


讯享网

‘Overlow’

你已经明白 Smi 是什么了,这儿就很容易理解了。

根本原因是,参数检查通过了,但函数的返回值却不是 Smi。例子:

例子2

我们继续来声明一个看起来相同的函数。

看起来一样的函数,结果却不相同。为什么呢?同样的函数检查却不相同?

不!这些检查是类型相关的,也就是说 – 引擎并不会提前做出假设,它仅在函数执行过程中做出调整和优化。因此,即使这两个函数看起来一样,但是路径(path)却不相同。

这个例子中,我们的函数是由 Crankshaft 优化。

一旦你不传给它 Smi,而是传递一个堆对象时,就会发生反优化。事实上,它与 “Not a Smi” 相反,所以我不会详细解释它。它仅仅检查了参数“a”?

‘wrong instance type’ – 有趣!目前为止,我们还没见过它!

很容易猜到,这次检查失败是因为你没有传递 string,或者没有传值。

最后 2 个原因和上面相同,但是检查第 2 个参数 “b”。

例子 3

我们来看一个稍微不同的例子。

在解释这个之前,我们要先确保已经了解 hidden map(也称 hidden class)。如上文中提到的,引擎会做很多假设来减少一些无用操作花费的时间。然而,我们也要了解元素 – 每个元素都有类型。V8 实现了 TypeFeedbackVector。推荐你阅读这篇文章了解更多详情。已知类型见 chromium.googlesource.com/v8/v8.git/+…

也有一些原生函数可以帮助我们检查元素是否匹配已有类型。它们的定义见上段链接,对应的原生名称见 chromium.googlesource.com/v8/v8.git/+… 。

现在再来看反优化。

显而易见。这是由于你给函数的第一个参数 “arr” 传递了 Smi。

很不幸,这种情况经常发生。

我们的 map(类型)是: 

因此,一旦“arr”中的元素不同于 Smi 元素,map 就不再匹配。当我们向它传递的参数不是普通数组而是其它类型时,这种情况就会发生。比如:

如果你想检查数组是否由 Smi 元素组成,可以使用上面提到的原生函数 。

好,我们现在来检查第二个参数 ,你很快就发现,它的反优化依赖于第二个参数。

举例:

‘not a heap number’ – 不是数字(注意不要与 Smi 混淆),举例:

太容易了!

也很容易理解!

还有一个例子 – 上面例子组合的情况。这儿就不详细解释了,还是留给你做练习吧 :)

给出结果,以免你没有安装 d8 :)

就到这儿,结束了!

我们看了 2 个非常简单的例子,希望你能明白总的思想。

想看你的函数反优化的情况,只要传递 就好。

总的来说,不要过度优化,因为可能会伤害代码可读性(比如函数 ele-at 的第 3 个例子)。你也可以传递字符串数组或其它,这没问题。然而,如果真的不需要优化,就不要(优化)。就第 1 个例子而言,我认为即使 2 个函数看起来相同,最好还是能分成 2 个不同名称的函数,这样其他开发者看到 concat 或者 sum,马上就知道这个函数的作用。

未来,你可以添加 string 特有的操作,比如 (a + b).toUpperCase(),而不需要对 sum 函数做任何特殊处理。

最后,你应该牢记过度优化可能会伤害可读性,最终导致不可维护的代码。尽量不要使用任何编译语言中不适用的奇怪模式。

小讯
上一篇 2025-05-16 20:26
下一篇 2025-05-03 10:05

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/165030.html