I am trying to find the most efficient way to execute a certain operation. I have a Cell
class which represents an x, y position on a matrix. A Cell
needs to be able to spawn neighbor cells, like this:
class Cell {
constructor(x, y) {
this.count = 0;
this.x = x;
this.y = y;
}
get neighbors() {
if (this._neighbors) return this._neighbors;
this.count++;
const { x, y } = this;
let neighbors = [];
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
if (!(i === 0 && j === 0)) {
neighbors.push(new Cell(x + i, y + j));
}
}
}
this._neighbors = neighbors;
return this._neighbors;
}
}
You can see that for a given new Cell
, the first time cell.neighbors
is accessed, it generates the neighbors, and assigns them to cell._neighbors
. Every subsequent access of cell.neighbors
simply returns the value that was already computed.
I compared this against a method wherein the neighbors are not an accessor method, but rather simply created as part of the class instance in the constructor:
class Cell {
constructor(x, y, makeNeighbors = true) {
this.count = 0;
this.x = x;
this.y = y;
if (makeNeighbors) {
this.neighbors = this.getNeighbors();
}
}
getNeighbors() {
this.count++;
const { x, y } = this;
let neighbors = [];
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
if (!(i === 0 && j === 0)) {
neighbors.push(new Cell(x + i, y + j, false));
}
}
}
return neighbors;
}
}
In this case, creating a new Cell(x, y)
will have the neighbors calculated when the instance is created. The makeNeighbors
boolean is to make sure that every subsequent created Cell
doesn't try to calculate its neighbors and put us in an inifite loop. This is working well.
I compared these two methods with jsbench.me, with the following code:
const cells = Array.from({ length: 100 }).map((_, i) => new Cell(5, 5));
cells.forEach((cell) => {
for (i = 0; i < 100; i++) {
const n = cell.neighbors;
}
});
You can see the results here. The first method is nearly 65% slower than the second method.
Why? What is happening here? In both cases, the double loop and "heavy work" is only performed once. The first case simply returns the class _property as a precomputed value from the get method, and the second case simply returns it as a class property. What is going on that assigning the property in the constructor has better performance than using the accessor method?