8

I wrote a simple Sudoku solver using backtracking in JS. In effort to be "purely functional" all my 9x9 puzzle arrays are immutable thus a new array is created whenever a new number is inserted.

Version 1 using new SudokuPuzzle

In the first version I use the new Puzzle(puzzle) approach to clone the object:

function SudokuPuzzle(obj) {
   if (obj instanceof SudokuPuzzle) {
      this.grid = obj.grid.slice(0);  // copy array
   } // ...
}

Then whenever I update the array I do the following:

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
}

Version 2 using Object.create()

I wrote another version where I use Object.create() instead and have a base object sudokuPuzzle that I inherit from to create new puzzles. Here is the clone() method:

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
}

My update method in this case is

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

Speed Tests

The first version using new is very fast using Node:

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

The second version using Object.create() is consistently over 10x slower:

$ time node Sudoku2.js
real        0m7.746s
user        0m7.647s
sys         0m0.091s

A similar question here noted that Object.create() is a lot slower in browsers, while I am also seeing a large disparity with node.js as well. I can certainly see timing discrepancies between JS engines, but a > 10x difference?!? Does anyone know why the difference is over an order of magnitude?!

You can find the source code here.

Update with Annotated Answer

Thanks to Bergi's answer below I changed the line in the clone method from

 var puzzle = Object.create(this); 

to this:

 var puzzle = Object.create(sudokuPuzzle); 

which avoids long and complex inheritance chains (i.e., I always inherit from the same base object) and I now get speed results comparable to using new. Thanks Bergi.

Community
  • 1
  • 1
wcochran
  • 10,089
  • 6
  • 61
  • 69

2 Answers2

5

You've already established that V8 fails to optimise Object.create as good as new (in contrast to SpiderMonkey). Or at least did historically. See also this blog post for details.

However, there is a second reason for this being a lot slower: your two codes have different results. You would have to use

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
};

to create clones that are equivalent to those created using new SudokuPuzzle().

The problem is that when you use Object.create(this), you are creating a new object that has a different prototype - namely, the this instance. Cloning very often from each other, you are creating a very complex inheritance hierarchy. And all these objects, with different prototypes, will have different hidden classes - which prevents optimisations.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 2
    Excellent! That was the ticket! I had not considered the complex inheritance hierarchy. I guess I was doing it wrong -- always inherit from the same object where possible! – wcochran Mar 23 '16 at 17:15
0

You may check other posts comparing Object.create and new, for me it's intuition - if you use the new keyword, then the engine knows what it wants to create. If you use Object.create the engine needs to do some extra work to check, override the type, ...

As it is written in the post, if you are creating only a few object, the performance shouldn't matter (7s? really?). However - JS isn't really built for reflection. So methods this.clone() or Object.create(this) are really not effective.

gusto2
  • 11,210
  • 2
  • 17
  • 36
  • I was impressed how well Node optimizes tail recursion -- its seems silly that they would't optimize `Object.create` -- its a core mechanism. Actually the backtracking algorithm is creating *hundreds of millions* of nodes (think of all the possibilities of filling in a Sudoku Puzzle in row-major order). The time difference is well over a full order of magnitude -- that is not a large difference! – wcochran Mar 23 '16 at 17:12