hinoki
Version:
sane, simple dependency injection and more
322 lines (275 loc) • 7.51 kB
JavaScript
;
var assert = require('assert');
var _ = require('lodash');
var hinoki = require('./hinoki');
var process = require('process');
var ITERATIONS = 2000;
var OPTIMIZED = 1;
var NOT_OPTIMIZED = 2;
////////////////////////////////////////////////////////////////////////////////
// helpers
function statusToString(status) {
switch(status) {
case 1: return 'optimized';
case 2: return 'not optimized';
case 3: return 'always optimized';
case 4: return 'never optimized';
case 6: return 'maybe deoptimized';
}
}
function optimize(func, args) {
func.apply(null, args);
%OptimizeFunctionOnNextCall(func);
func.apply(null, args);
return func;
}
function assertOptimized(func) {
var status = %GetOptimizationStatus(func);
assert.equal(OPTIMIZED, status, %FunctionGetName(func) + ': ' + statusToString(status));
}
function assertNotOptimized(func) {
var status = %GetOptimizationStatus(func);
assert.equal(NOT_OPTIMIZED, status, %FunctionGetName(func) + ': ' + statusToString(status));
}
function assertOptimizable(func, args) {
optimize(func, args);
assertOptimized(func);
}
function assertNotOptimizable(func) {
optimize(func);
assertNotOptimized(func);
}
function diffToNs(diff) {
return diff[0] * 1e9 + diff[1];
}
function nsToS(ns) {
return ns / 1e9;
}
function nsToMs(ns) {
return ns / 1e6;
}
function benchmark(func, getArgs) {
// measurements
var ms = {
name: %FunctionGetName(func),
iterations: ITERATIONS,
firstOptAt: null,
optCount: 0,
firstDeoptAt: null,
deoptCount: 0,
minNs: null,
maxNs: null,
sumNs: 0
};
console.log('')
console.log('START ' + ms.name);
console.log('')
console.time('END ' + ms.name);
for (var i = 0; i < ITERATIONS; i++) {
var args = getArgs();
var statusBefore = %GetOptimizationStatus(func);
// BEGIN
var time = process.hrtime();
func.apply(null, args);
var elapsed = diffToNs(process.hrtime(time));
// END
if (!ms.minNs) ms.minNs = elapsed;
if (!ms.maxNs) ms.maxNs = elapsed;
ms.maxNs = Math.max(ms.maxNs, elapsed);
ms.minNs = Math.min(ms.minNs, elapsed);
ms.sumNs += elapsed;
var statusAfter = %GetOptimizationStatus(func);
if (statusBefore != OPTIMIZED && statusAfter == OPTIMIZED) {
// OPT :)
if (!ms.firstOptAt) ms.firstOptAt = i;
ms.optCount++;
} else if (statusBefore != NOT_OPTIMIZED && statusAfter == NOT_OPTIMIZED) {
// DEOPT :(
if (!ms.firstDeoptAt) ms.firstDeoptAt = i;
ms.deoptCount++;
}
}
console.log('')
console.timeEnd('END ' + ms.name);
console.log('')
ms.sumMs = nsToMs(ms.sumNs);
ms.sumS = nsToS(ms.sumNs);
ms.avgNs = ms.sumNs / ITERATIONS;
ms.opsPerSec = ITERATIONS / ms.sumS;
ms.finalState = statusToString(%GetOptimizationStatus(func));
return ms;
}
////////////////////////////////////////////////////////////////////////////////
// assertions
// check that V8s crankshaft naturally (on its own) optimizes all functions
// these so far are insanely fast
// isObjectLike
var measurements = benchmark(
hinoki.isObjectLike,
function() {
var choices = [{}, 1, 3.5, 'foo', function() {}];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.isObjectLike);
// isPromise
var measurements = benchmark(
hinoki.isPromise,
function() {
var choices = [{}, 1, 'foo', {then: 6}, {then: function() {}}];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.isPromise);
// isError
var measurements = benchmark(
hinoki.isError,
function() {
var choices = [new Error('error'), 1, 'foo', {then: function() {}}];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.isError);
// parseFunctionArguments
var measurements = benchmark(
hinoki.parseFunctionArguments,
function() {
var choices = [
function(a) {},
function(a, b) {},
function(a, b, c) {},
function(a, b, c, d) {},
function(a, b, c, d, e) {},
function(a, b, c, d, e, f) {}
]
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.parseFunctionArguments);
// getDependencies
var measurements = benchmark(
hinoki.getDependencies,
function() {
var choices = [
function(a) {},
function(a, b) {},
function(a, b, c) {},
function(a, b, c, d) {},
function(a, b, c, d, e) {},
function(a, b, c, d, e, f) {}
];
choices[0].$inject = ['x', 'y', 'z'];
choices[2].$inject = [];
choices[4].$inject = ['y', 'z'];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.getDependencies);
assertOptimized(hinoki.parseFunctionArguments);
assertOptimized(_.isArray);
// getDependenciesCached
var measurements = benchmark(
hinoki.getDependenciesCached,
function() {
var choices = [
function(a) {},
function(a, b) {},
function(a, b, c) {},
function(a, b, c, d) {},
function(a, b, c, d, e) {},
function(a, b, c, d, e, f) {}
];
choices[0].$inject = ['x', 'y', 'z'];
choices[2].$inject = [];
choices[4].$inject = ['y', 'z'];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.getDependenciesCached);
assertOptimized(hinoki.parseFunctionArguments);
assertOptimized(_.isArray);
// arrayOfStringsHasDuplicates
var measurements = benchmark(
hinoki.arrayOfStringsHasDuplicates,
function() {
var choices = [
['a', 'b', 'c', 'd', 'e'],
['a', 'b', 'a', 'a', 'e'],
['c', 'd', 'e'],
['c', 'c', 'e'],
['e', 'b', 'c', 'd', 'e']
];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.arrayOfStringsHasDuplicates);
assertOptimized(_.has);
// defaultResolver
var measurements = benchmark(
hinoki.defaultResolver,
function() {
var nameChoices = ['a', 'b', 'c'];
var containerChoices = [
{},
{values: {}},
{values: {a: 5}},
{factories: {}, values: {}},
{factories: {b: function() {}}, values: {}},
{factories: {a: function() {}}},
{}
];
return [_.sample(nameChoices), _.sample(containerChoices)];
}
);
console.log(measurements);
assertOptimized(hinoki.defaultResolver);
// TODO make these optimized
// defaultResolver seems to break all the optimization
// assertOptimized(hinoki.isObjectLike);
// assertOptimized(_.isFunction);
// assertOptimized(_.isUndefined);
// assertOptimized(hinoki.getDependenciesCached);
// assertOptimized(hinoki.ValueResult);
// assertOptimized(hinoki.FactoryResult);
// assertOptimizable(_.isFunction, function() {});
// assertOptimizable(hinoki.defaultResolver, ['a', {}]);
// coerceIntoArray
var measurements = benchmark(
hinoki.coerceIntoArray,
function() {
var choices = [
null,
undefined,
1,
'foo',
[],
[1],
['foo', 1, 1]
];
return [_.sample(choices)];
}
);
console.log(measurements);
assertOptimized(hinoki.coerceIntoArray);
assertOptimized(_.isArray);
// notOptimizable
function notOptimizable() {
try {
return 1 + 7;
} catch (e) {
return 8;
}
}
assertNotOptimizable(notOptimizable);
////////////////////////////////////////////////////////////////////////////////
// other assertions
// assert.ok(hinoki.isError(new hinoki
console.log('OK');