小编典典

为什么在 V8 中使用此代码片段 <= 比 < 慢?

all

我正在阅读幻灯片Breaking the Javascript Speed Limit with
V8
,并且有一个类似下面代码的示例。我无法弄清楚为什么<=<这种情况下要慢,有人可以解释一下吗?任何意见表示赞赏。

减缓:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
}

(提示:primes 是一个长度为 prime_count 的数组)

快点:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i < this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
}

[More Info] 速度提升显着,在我本地环境测试,结果如下:

V8 version 7.3.0 (candidate)

减缓:

 time d8 prime.js
 287107
 12.71 user 
 0.05 system 
 0:12.84 elapsed

快点:

time d8 prime.js
287107
1.82 user 
0.01 system 
0:01.84 elapsed

阅读 77

收藏
2022-08-29

共1个答案

小编典典

我在 Google 从事 V8 工作,并希望在现有答案和评论的基础上提供一些额外的见解。

作为参考,以下是幻灯片中的完整代码示例:

var iterations = 25000;

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(iterations);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }
  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  var p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < iterations) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

首先,性能差异与<and<=运算符没有直接关系。所以请不要为了避免<=在你的代码中跳过箍,因为你在 Stack Overflow
上读到它很慢——它不是!


其次,人们指出数组是“有孔的”。这在 OP 帖子中的代码片段中并不清楚,但是当您查看初始化代码时很清楚this.primes

this.primes = new Array(iterations);

这会导致数组在 V8 中具有HOLEY元素类型,即使该数组最终完全填充/打包/连续。一般来说,空洞数组上的操作比压缩数组上的操作要慢,但在这种情况下,差异可以忽略不计: 每次
我们this.primes[i]isPrimeDivisible. 没什么大不了!

TL;DR 数组HOLEY不是这里的问题。


其他人指出,代码读取越界。通常建议避免读取超出数组长度的内容,在这种情况下,确实可以避免性能大幅下降。但为什么呢?V8
可以处理其中一些越界场景,而对性能的影响很小。
那么,这个特殊案例有什么特别之处呢?

越界读取导致在this.primes[i]这一undefined行:

if ((candidate % this.primes[i]) == 0) return true;

这给我们带来 了真正的问题%运算符现在与非整数操作数一起使用!

  • integer % someOtherInteger可以非常有效地计算;JavaScript 引擎可以为这种情况生成高度优化的机器代码。

  • integer % undefined另一方面Float64Mod,由于undefined表示为双精度,因此效率较低。

确实可以通过在这一行更改<=into来改进代码片段:<

for (var i = 1; i <= this.prime_count; ++i) {

…不是因为<=它是某种比 更好的运算符<,而是因为这避免了在这种特殊情况下的越界读取。

2022-08-29