Skip to content

Commit

Permalink
feat(performance): improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikCupal committed Jul 23, 2017
1 parent 59df20d commit 10a2b79
Show file tree
Hide file tree
Showing 17 changed files with 3,387 additions and 5,660 deletions.
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
node_modules/
build/
.vscode/
dist/
es6/
typings/
.vscode/
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ node_js:
before_script:
- npm prune
script:
- npm run test
- npm run clean
- npm run build
- npm t
after_success:
- npm run semantic-release
branches:
Expand Down
76 changes: 76 additions & 0 deletions benchmark/benchmarkMemoization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// const triemoize = require('../dist').default
const triemoize = require('../')
const Table = require('cli-table2')
const fastMemoize = require('fast-memoize')
const Benchmark = require('benchmark')
const { sort, map, forEach, toPairs, pipe } = require('ramda')

const yellow = '\x1b[33m'
const white = '\x1b[37m'

const renderResults = ({
functionName = undefined,
functionArguments = undefined,
results = [],
} = {}) => {
console.log('')

if (functionName) console.log(yellow, 'Function:', white, functionName)
if (functionArguments) console.log(yellow, 'Passed arguments:', white, functionArguments)

console.log('')

const transformedResults = map(result => [
result.target.name,
result.target.hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
], results)

const table = new Table({ head: ['METHOD', 'OPS/S'] })
table.push(...transformedResults)

console.log(table.toString())
}

const defaultMemoizers = {
'fast-memoize': fastMemoize,
'triemoize': triemoize,
}

const benchmarkMemoization = (functionToMemoize, passedArguments, memoizers = defaultMemoizers) => {
if (functionToMemoize && passedArguments) {
let results = []

const sortResults = sort((a, b) => a.target.hz < b.target.hz ? 1 : -1)

const onCycle = event => results.push(event)

const onComplete = () => renderResults({
functionName: functionToMemoize.name,
functionArguments: passedArguments,
results: sortResults(results),
})

const memoizedFunctions = map(memoizer => memoizer(functionToMemoize), memoizers)

const functionsToBenchmark = Object.assign({}, memoizedFunctions, { 'vanilla': functionToMemoize })

const benchmark = new Benchmark.Suite()

pipe(
toPairs,
forEach(([memoizerName, memoizedFunction]) => {
benchmark.add(memoizerName, () => memoizedFunction(...passedArguments))
})
)(functionsToBenchmark)

benchmark
.on('cycle', onCycle)
.on('complete', onComplete)
.run()

} else {
console.warn('Error benchmarking!')
}
}

module.exports = benchmarkMemoization
13 changes: 13 additions & 0 deletions benchmark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const benchmarkMemoization = require('./benchmarkMemoization')

const fibonacci = n => n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
const add = (a, b) => a + b
const double = n => 2 * n
const identity = a => a

// benchmarkMemoization(fibonacci, [15])
// benchmarkMemoization(double, [2])
// benchmarkMemoization(add, [1, 2])
benchmarkMemoization(identity, [2])
// benchmarkMemoization(identity, [3])
benchmarkMemoization(identity, [{}])
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Options {
primitivesCacheLimit?: number;
}

export declare function memoize<F extends Function>(fn: F, options?: Options): F;

export default memoize;
146 changes: 146 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"use strict";

var cacheSymbol = Symbol();
var hasResult = Symbol();
var result = Symbol();
var objectsCache = Symbol();
var limitedObject = Symbol();
var primitivesKeysQueue = Symbol();
var limit = Symbol();
var noResult = Symbol();

function mapHas(map, key) {
if (typeof key !== 'object') {
return map.hasOwnProperty(key);
} else {
if (map[objectsCache]) {
return map[objectsCache].has(key);
} else {
return false;
}
}
}

function mapGet(map, key) {
if (typeof key !== 'object') {
return map[key];
} else {
return map[objectsCache].get(key);
}
}

function mapSet(map, key, value) {
if (typeof key !== 'object') {
if (map[limitedObject]) {
var queue = map[primitivesKeysQueue];
if (queue.length >= map[limit]) {
delete map[queue.shift()];
}
queue.push(key);
}
map[key] = value;
} else {
if (!map[objectsCache]) {
map[objectsCache] = new WeakMap();
}
map[objectsCache].set(key, value);
}
}

function createCombinedMap(primitivesLimit) {
var combinedMap = {};
combinedMap[cacheSymbol] = true;

if (typeof primitivesLimit === 'number') {
combinedMap[limitedObject] = true;
combinedMap[primitivesKeysQueue] = [];
combinedMap[limit] = primitivesLimit;
}

return combinedMap;
}

function get(cache, params, remainingParamsLength) {
if (remainingParamsLength === 0) {
return cache[result];
}
var currentParamsKey = params[params.length - remainingParamsLength];
var hasKey = mapHas(cache, currentParamsKey);
if (hasKey) {
var keyValue = mapGet(cache, currentParamsKey);
if (remainingParamsLength === 1) {
if (typeof keyValue === 'object' && keyValue[cacheSymbol]) {
return keyValue[result];
} else {
return keyValue;
}
}
return get(keyValue, params, remainingParamsLength - 1);
}
return noResult;
}

function set(value, cache, cacheLimit, params, remainingParamsLength) {
if (remainingParamsLength === 0) {
cache[hasResult] = true;
cache[result] = value;
return;
}
var currentParamsKey = params[params.length - remainingParamsLength];
var hasKey = mapHas(cache, currentParamsKey);
var nextCache;
if (remainingParamsLength === 1) {
if (hasKey) {
nextCache = mapGet(cache, currentParamsKey);
nextCache[hasResult] = true;
nextCache[result] = value;
return;
}
else {
mapSet(cache, currentParamsKey, value);
}
return;
}
if (hasKey) {
var keyValue = mapGet(cache, currentParamsKey);
if (typeof keyValue === 'object' && keyValue[cacheSymbol]) {
set(value, keyValue, cacheLimit, params, remainingParamsLength - 1);
}
else {
nextCache = createCombinedMap(cacheLimit);
nextCache[hasResult] = true;
nextCache[result] = keyValue;
mapSet(cache, currentParamsKey, nextCache);
set(value, nextCache, cacheLimit, params, remainingParamsLength - 1);
}
return;
}
nextCache = createCombinedMap(cacheLimit);
mapSet(cache, currentParamsKey, nextCache);
set(value, nextCache, cacheLimit, params, remainingParamsLength - 1);
}

function memoize(fn, options) {
var primitivesCacheLimit = options && options.primitivesCacheLimit;
var cache = createCombinedMap(primitivesCacheLimit);
function memoizedFn() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}

var possibleResult = get(cache, args, _len);
if (possibleResult === noResult) {
var result = fn.apply(undefined, args);
set(result, cache, primitivesCacheLimit, args, _len);
return result;
} else {
return possibleResult;
}
}
return memoizedFn;
}

module.exports = memoize;
module.exports.memoize = memoize;
module.exports.default = memoize;
module.exports.__esModule = true;
10 changes: 5 additions & 5 deletions src/lib/index.test.ts → index.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { memoize } from '.'
const memoize = require('.')

test('memoize', () => {
let sumCalledCount = 0
let getStringsShorterThanCalledCount = 0

const sum = (...args: number[]): number => {
const sum = (...args) => {
sumCalledCount++
return args.reduce((acc, n) => acc + n, 0)
}
const getStringsShorterThan = (array: string[], n: number): string[] => {
const getStringsShorterThan = (array, n) => {
getStringsShorterThanCalledCount++
return array.filter(item => item.length <= n)
}
Expand Down Expand Up @@ -52,7 +52,7 @@ test('memoize', () => {
test('memoize limited', () => {
let sumCalledCount = 0

const sum = (...args: number[]): number => {
const sum = (...args) => {
sumCalledCount++
return args.reduce((acc, n) => acc + n, 0)
}
Expand Down Expand Up @@ -89,7 +89,7 @@ test('memoize limited', () => {
// test('memoize no arg function', () => {
// let fnCalledCount = 0

// const fn = (): number => {
// const fn = () => {
// fnCalledCount++
// return 1
// }
Expand Down
Loading

0 comments on commit 10a2b79

Please sign in to comment.