javascript - JsPerf : ParseInt vs Plus conversion

标签 javascript performance type-conversion parseint

我尝试使用以下 jsperf 来探测 plus (+) 转换比 parseInt 更快,结果让我感到惊讶:

Parse vs Plus

制备代码

<script>
  Benchmark.prototype.setup = function() {
    var x = "5555";
  };
</script>

解析样本
var y = parseInt(x); //<---80 million loops

加样
var y = +x; //<--- 33 million loops

原因是因为我使用“Benchmark.prototype.setup”来声明我的变量,但我不明白为什么

看第二个例子:

Parse vs Plus (local variable)
<script>
  Benchmark.prototype.setup = function() {
    x = "5555";
  };
</script>

解析样本
var y = parseInt(x); //<---89 million loops

加样
var y = +x; //<--- 633 million loops

有人可以解释结果吗?

谢谢

最佳答案

在第二种情况下 +更快,因为在这种情况下,V8 实际上将其移出基准测试循环 - 制作基准测试循环 .

这是由于当前优化管道的某些特殊性造成的。但在我们进入血腥细节之前,我想提醒一下 Benchmark.js 是如何工作的。

要测量您编写的测试用例,需要 Benchmark.prototype.setup您还提供了测试用例本身并动态生成了一个看起来是 的函数大约 像这样(我跳过了一些不相关的细节):

function (n) {
  var start = Date.now();

  /* Benchmark.prototype.setup body here */
  while (n--) {
    /* test body here */
  }

  return Date.now() - start;
}

创建函数后,Benchmark.js 会调用它来测量特定迭代次数的操作 n .这个过程重复几次:生成一个新函数,调用它来收集一个测量样本。在样本之间调整迭代次数以确保函数运行足够长的时间以提供有意义的测量。

这里要注意的重要事项是
  • 您的情况和 Benchmark.prototype.setup是文本内联的;
  • 您要测量的操作有一个循环;

  • 本质上我们讨论为什么下面的代码带有局部变量x
    function f(n) {
      var start = Date.now();
    
      var x = "5555"
      while (n--) {
        var y = +x
      }
    
      return Date.now() - start;
    }
    

    运行速度比带有全局变量 x 的代码慢

    function g(n) {
      var start = Date.now();
    
      x = "5555"
      while (n--) {
        var y = +x
      }
    
      return Date.now() - start;
    }
    

    (注意:这种情况在问题本身中称为局部变量,但事实并非如此, x 是全局的 )

    当您使用足够大的 n 值执行这些函数时会发生什么,例如 f(1e6) ?

    当前的优化管道以一种特殊的方式实现 OSR。它不是生成优化代码的 OSR 特定版本并在以后丢弃它,而是生成一个既可用于 OSR 又可用于正常条目的版本,甚至可以在我们需要在同一循环中执行 OSR 时重复使用。这是通过将一个特殊的 OSR 入口块注入(inject)控制流图中的正确位置来完成的。

    OSR version of the control flow graph

    在构建函数的 SSA IR 时注入(inject) OSR 入口块,并且它急切地将所有局部变量从传入的 OSR 状态中复制出来。结果V8看不到本地x实际上是一个常量,甚至会丢失有关其类型的任何信息。用于后续优化传递 x2看起来它可以是任何东西。

    x2可以是任何表达式 +x2也可以有任意的副作用(例如,它可以是一个带有 valueOf 的对象)。这可以防止循环不变代码运动传递移动 +x2跳出循环。

    为什么是 g比...快? V8 在这里有一个技巧。它跟踪包含常量的全局变量:例如在这个基准全局x始终包含 "5555"所以 V8 只是取代了 x访问它的值并将这个优化的代码标记为依赖于 x 的值。 .如果有人更换 x与所有依赖代码不同的值将被取消优化。全局变量也不属于 OSR 状态,也不参与 SSA 重命名,因此 V8 不会被合并 OSR 和正常进入状态的“虚假”φ 函数混淆。这就是为什么V8优化时g它最终在循环体中生成以下 IR(左侧的红色条纹显示了循环):

    IR before LICM

    注:+x编译为 x * 1 ,但这只是一个实现细节。

    后来 LICM 只会执行此操作并将其移出循环,而不会对循环本身产生任何兴趣。这成为可能,因为现在 V8 知道 * 的两个操作数是原语 - 所以可以有 副作用。

    IR after LICM

    这就是为什么 g更快,因为空循环显然比非空循环快。

    这也意味着基准测试的第二个版本实际上并没有测量您希望它测量的内容,而第一个版本确实掌握了 parseInt(x) 之间的一些差异。和 +x性能更靠运气:您在 V8 的当前优化管道 (Crankshaft) 中遇到了限制,阻止它吞噬整个微基准测试。

    关于javascript - JsPerf : ParseInt vs Plus conversion,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28457585/

    相关文章:

    javascript - Chrome 32 中标签更改的错误

    javascript - 使用 Chart.js 绘制条形图

    设备上的 Android Studio 慢速调试

    iphone - 使用 Instruments 对性能进行基准测试

    php - 哪些 PHP 框架可以处理大型应用程序?

    javascript - 一系列 promise 如何与 reduce 一起工作?

    javascript - 如果值没有改变,vue js禁用输入

    java - 为什么 DozerConverter 不工作?

    c++ - 以 std::array of doubles 作为参数或将 double 单独作为参数转换函数

    c# - 如何在 C# 中创建一个 "independent"(datacontainer) 类来保存来自另一个类的数据? (松弛/JSON相关)