为什么Node的Object.create(foo)比new Foo()慢得多?

8
我使用JS编写了一个简单的数独求解器,采用回溯算法。为了实现“纯函数式”的要求,我将所有9x9的谜题数组变成了不可变的,因此每次插入新数字时都会创建一个新的数组。

第一版采用new SudokuPuzzle

在第一个版本中,我使用new Puzzle(puzzle)方法来克隆对象:
function SudokuPuzzle(obj) {
   if (obj instanceof SudokuPuzzle) {
      this.grid = obj.grid.slice(0);  // copy array
   } // ...
}

每当我更新数组时,我会执行以下操作:

SudokuPuzzle.prototype.update = function(row, col, num) {
    var puzzle = new SudokuPuzzle(this); // clone puzzle
    puzzle.grid[row*9 + col] = num;      // mutate clone
    return puzzle;                       // return clone
}

使用Object.create()的第二版

我写了另一个版本,其中我使用Object.create(),并拥有一个基础对象sudokuPuzzle,我从中继承以创建新的谜题。这是clone()方法:

sudokuPuzzle.clone = function() {
   var puzzle = Object.create(this); // create puzzle from sudokuPuzzle
   puzzle.grid = this.grid.slice(0); // copy array
   return puzzle;                    // return newly minted puzzle
}

在这种情况下,我的更新方法是

sudokuPuzzle.update = function(row, col, num) {
   var puzzle = this.clone();       // clone puzzle
   puzzle.grid[row*9 + col] = num;  // mutate clone
   return puzzle;                   // return clone
}

速度测试

在Node中使用new的第一个版本非常快:

$ time node Sudoku.js
real    0m0.720s
user    0m0.699s
sys     0m0.016s

第二个版本使用Object.create(),速度始终慢了10倍以上:
$ time node Sudoku2.js
real        0m7.746s
user        0m7.647s
sys         0m0.091s

有人在这里提到,Object.create()在浏览器中速度非常慢,而我在node.js中也看到了很大的差距。我当然可以看到JS引擎之间的时间差异,但是超过一个数量级的差异?!有人知道为什么差异如此之大吗?

您可以在这里找到源代码。

带注释答案的更新

感谢Bergi在下面的回答中,我将clone方法中的行从

 var puzzle = Object.create(this); 

转换为:

 var puzzle = Object.create(sudokuPuzzle); 

这种方法避免了长而复杂的继承链 (即,我总是从同一个基本对象继承)。现在,我得到了与使用 new 相当的速度结果。感谢 Bergi。

2个回答

5
您已经确定V8无法像SpiderMonkey那样对Object.create进行优化,至少在历史上是这样。有关详细信息,请参见此博客文章。然而,这变慢的第二个原因是您的两个代码具有不同的结果。您需要使用
SudokuPuzzle.prototype.clone = function() {
   var puzzle = Object.create(SudokuPuzzle.prototype); // a new instance, without `new`
   puzzle.grid = this.grid.slice(0); // copy array (usually initialised in constructor)
   return puzzle;                    // return newly minted puzzle
};

要创建与使用new SudokuPuzzle()相同的克隆版本。

问题在于当您使用Object.create(this)时,您正在创建一个具有不同原型的新对象 - 即 this 实例。互相克隆,您正在创建一个非常复杂的继承层次结构。而所有这些具有不同原型的对象将具有不同的隐藏类,这会阻止优化


2
太棒了!就是这个办法!我没有考虑到复杂的继承层次结构。我猜我做错了 - 尽可能从同一个对象继承! - wcochran

0

你可以查看其他帖子比较Object.create和new,对我来说这是直觉 - 如果你使用new关键字,那么引擎知道它想要创建什么。如果你使用Object.create,引擎需要做一些额外的工作来检查、覆盖类型等。

正如帖子中所写,如果你只创建了几个对象,性能不应该成为问题(7秒?真的吗?)。然而 - JS并不是为反射而构建的。因此,方法this.clone()Object.create(this)并不是非常有效。


我对Node如何优化尾递归印象深刻——他们不优化Object.create似乎有些愚蠢,因为它是一个核心机制。实际上,回溯算法正在创建数亿个节点(想象一下按行主顺序填充数独谜题的所有可能性)。时间差异超过了一个数量级——这并不是一个很大的差异! - wcochran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接