Skip to content

Commit

Permalink
Implemented and benchmarked array-map improvement (preallocating outp…
Browse files Browse the repository at this point in the history
…ut array)
  • Loading branch information
vzhou842 committed Apr 24, 2018
1 parent c124287 commit a4854d2
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 16 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ faster.js is 556.3% faster (5.008μs) than native
faster.js is 228.1% faster (11.379μs) than native

array-map large
✓ native x 169,961 ops/sec ±1.51% (57 runs sampled)
✓ faster.js x 706,781 ops/sec ±0.64% (59 runs sampled)
faster.js is 315.8% faster (4.469μs) than native
✓ native x 179,800 ops/sec ±1.00% (58 runs sampled)
✓ faster.js x 1,706,593 ops/sec ±0.25% (56 runs sampled)
faster.js is 849.2% faster (4.976μs) than native

array-reduce large
✓ native x 200,425 ops/sec ±1.01% (55 runs sampled)
Expand Down
38 changes: 38 additions & 0 deletions bench/array-map-preallocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const benchmark = require('benchmark');
const utils = require('./utils');

benchmark.options.onAbort = event => console.error(event.currentTarget.error);

['small', 'medium', 'large'].forEach(arraySize => {
const suite = new benchmark.Suite();
const title = `array-map-preallocation ${arraySize}`;

suite.add('without preallocation', () => {
const _defined = utils.randomArray(arraySize);
const _defined2 = (e, i) => e + i;
const results = [];
for (let _i = 0; _i <= _defined.length - 1; _i++) {
results.push(_defined2(_defined[_i], _i, _defined));
}
});
suite.add('with preallocation', () => {
const _defined = utils.randomArray(arraySize);
const _defined2 = (e, i) => e + i;
const results = new Array(_defined.length);
for (let _i = 0; _i <= _defined.length - 1; _i++) {
results[_i] = _defined2(_defined[_i], _i, _defined);
}
});

suite.on('start', () => console.log(' ' + title));
suite.on('cycle', event => console.log(" ✓ " + event.target));
suite.on('complete', function() {
const slowest = this.filter('slowest')[0];
const fastest = this.filter('fastest')[0];
const microsecDelta = ((1000000 / slowest.hz) - (1000000 / fastest.hz)).toFixed(3);
const percentFaster = (100 * (fastest.hz - slowest.hz) / slowest.hz).toFixed(1);
console.log(fastest.name + ' is ' + percentFaster + '% faster (' + microsecDelta + 'μs) than ' + slowest.name + '\n');
});

suite.run();
});
43 changes: 31 additions & 12 deletions src/optimizations/array-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ export default t => ({
const assignee = expression.left;

// If the array is the assignee, we need a temp var to hold the array.
const array = expression.right.callee.object;
let array = expression.right.callee.object;
if (t.isIdentifier(array) && array.name === assignee.name) {
mapPath.get('callee.object').replaceWith(defineId(t, path, array, 'const', 'arr'));
array = defineId(t, path, array, 'const', 'arr');
mapPath.get('callee.object').replaceWith(array);
} else {
array = getArray(t, path, mapPath);
}

const initAssignment = t.assignmentExpression('=', assignee, t.arrayExpression());
const initAssignment = t.assignmentExpression('=', assignee, arrayAllocExpression(t, array));
path.insertBefore(t.expressionStatement(initAssignment));

path.replaceWith(forLoop(t, path, assignee, mapPath));
path.replaceWith(forLoop(t, path, array, assignee, mapPath));
},

VariableDeclaration(path, state) {
Expand All @@ -51,25 +54,41 @@ export default t => ({
return;
}

const mapPath = path.get('declarations.0.init');

const assignee = declaration.id;
const array = getArray(t, path, mapPath);

const mapPath = path.get('declarations.0.init');
path.insertAfter(forLoop(t, path, assignee, mapPath));
mapPath.replaceWith(t.arrayExpression());
path.insertAfter(forLoop(t, path, array, assignee, mapPath));
mapPath.replaceWith(arrayAllocExpression(t, array));
},
});

function forLoop(t, path, assignee, mapPath) {
const array = defineIdIfNeeded(t, mapPath.get('callee.object'), path);
/**
* Returns an expression representing:
* new Array(array.length)
*/
function arrayAllocExpression(t, array) {
return t.newExpression(t.identifier('Array'), [
t.memberExpression(array, t.identifier('length')),
]);
}

function getArray(t, path, mapPath) {
return defineIdIfNeeded(t, mapPath.get('callee.object'), path);
}

function forLoop(t, path, array, assignee, mapPath) {
const func = extractDynamicFuncIfNeeded(t, mapPath, path);
const i = path.scope.generateUidIdentifier('i');

const newMapCall = t.callExpression(func, [t.memberExpression(array, i, true), i, array]);
const forBody = t.blockStatement([
t.expressionStatement(
t.callExpression(
t.memberExpression(assignee, t.identifier('push')),
[newMapCall]
t.assignmentExpression(
'=',
t.memberExpression(assignee, i, true),
newMapCall
)
),
]);
Expand Down
2 changes: 1 addition & 1 deletion test/exec/array-filter/literal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ function f(e, i, a) {
return e > 1;
}

const results = [1, 2, -1, 5].map(f);
const results = [1, 2, -1, 5].filter(f);

1 comment on commit a4854d2

@vzhou842
Copy link
Owner Author

@vzhou842 vzhou842 commented on a4854d2 Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implements #2

Results of running node bench/array-map-preallocation.js:

  array-map-preallocation small
    ✓ without preallocation x 15,121,514 ops/sec ±0.82% (86 runs sampled)
    ✓ with preallocation x 17,445,846 ops/sec ±0.46% (92 runs sampled)
with preallocation is 15.4% faster (0.009μs) than without preallocation

  array-map-preallocation medium
    ✓ without preallocation x 5,612,299 ops/sec ±0.68% (92 runs sampled)
    ✓ with preallocation x 7,665,726 ops/sec ±0.48% (93 runs sampled)
with preallocation is 36.6% faster (0.048μs) than without preallocation

  array-map-preallocation large
    ✓ without preallocation x 737,544 ops/sec ±0.54% (94 runs sampled)
    ✓ with preallocation x 1,693,228 ops/sec ±0.45% (95 runs sampled)
with preallocation is 129.6% faster (0.765μs) than without preallocation

Please sign in to comment.