From fa66ccaf1f34adf8b12b32928f8473961250098b Mon Sep 17 00:00:00 2001 From: Anton Korzunov Date: Mon, 29 Apr 2019 09:29:12 +1000 Subject: [PATCH] try faster shallowEqual --- benchmarks/deepEqual.js | 108 ++++++++++++++++++++++++++++++++++++++++ src/index.js | 75 +++++++++++----------------- src/objectTrie.js | 23 +++++++++ 3 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 benchmarks/deepEqual.js create mode 100644 src/objectTrie.js diff --git a/benchmarks/deepEqual.js b/benchmarks/deepEqual.js new file mode 100644 index 0000000..8b26931 --- /dev/null +++ b/benchmarks/deepEqual.js @@ -0,0 +1,108 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ +const Benchmark = require('benchmark'); +const {proxyShallowEqual, proxyCompare, proxyEqual, deepDeproxify} = require('../'); +const {weakMemoizeArray} = require("../lib/weakMemoize"); + +const suite = new Benchmark.Suite(); + +const s1 = { + a: 1, + b: 2, + c: 3, + d: [[ + 1, 2, 3 + ]], + e: {a: {b: {c: 42}}} +}; + +const s2 = s1; + +const s3 = Object.assign({}, s1); + +const s4 = Object.assign({}, s1, { + e: Object.assign({}, s1.e, {b: {c: 42}}) +}); + +const s5 = Object.assign({}, s1, { + e: Object.assign({}, s1.e, {b: {c: 42}}) +}); + +const left = s1; +const right = s5; + +//const locations = [".d", ".d.0", ".d.0.0", ".d.0.1", ".d.0.2"]; + const locations = [".e",".e.a",".e.a.b",".a",'.b']; +// const locations = [".a",".b",".c",".cc",".ccc",".cccc",".aa",".bb"]; + +const get = (target, path) => { + let result = target; + for (let i = 1; i < path.length && result; ++i) { + const key = path[i]; + result = result[key] + } + return result; +}; + +const EDGE = 'EDGE'; + +const buildObjTrie = (lines) => { + const root = {}; + for (let i = 0; i < lines.length; ++i) { + const path = lines[i].split('.'); + let node = root; + const lastIndex = path.length - 1; + for (let j = 1; j < lastIndex; ++j) { + const item = path[j]; + if (!node[item] || node[item] === EDGE) { + node[item] = {}; + } + node = node[item]; + } + node[path[lastIndex]] = EDGE; + } + return root; // FIXME +}; + +const memoizedBuildTrie = weakMemoizeArray(buildObjTrie); + +const proxyShallowEqual2 = (a, b, affected) => { + const root = memoizedBuildTrie(affected); + const walk = (la, lb, node) => { + if (la === lb || deepDeproxify(la) === deepDeproxify(lb)) { + return true; + } + if (node === EDGE) { + return false; + } + const items = Object.keys(node); + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + if (!walk( + get(la, ['', item]), // FIXME + get(lb, ['', item]), // FIXME + node[item], + )) { + return false; + } + } + return true; + }; + return walk(a, b, root); +}; + +suite.add('proxyShallowEqual-0', () => { + proxyShallowEqual(left, right, locations); +}); + +suite.add('proxyShallowEqual-2', () => { + proxyShallowEqual2(left, right, locations); +}); + +suite.add('proxyShallowEqual-3', () => { + proxyShallowEqual(left, right, locations); +}); + + +suite.on('cycle', e => console.log(String(e.target))); + +suite.run({async: true}); diff --git a/src/index.js b/src/index.js index ce88db3..f2b0cf5 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import {str as crc32_str} from "crc-32"; import ProxyPolyfill from './proxy-polyfill'; import {getCollectionHandlers, shouldInstrument} from "./shouldInstrument"; import {weakMemoizeArray} from "./weakMemoize"; +import {EDGE, memoizedBuildTrie} from "./objectTrie"; const hasProxy = typeof Proxy !== 'undefined'; const ProxyConstructor = hasProxy ? Proxy : ProxyPolyfill(); @@ -28,7 +29,7 @@ const isProxyfied = object => object && typeof object === 'object' ? ProxyToStat const deproxify = (object) => object && typeof object === 'object' ? ProxyToState.get(object) : object || object; -const deepDeproxify = (object) => { +export const deepDeproxify = (object) => { if (object && typeof object === 'object') { let current = object; while (ProxyToState.has(current)) { @@ -285,63 +286,47 @@ const proxyCompare = (a, b, locations) => { return ret; }; -const nestedSort = (a, b) => { - for (let i = 0; ; ++i) { - const ac = a.charCodeAt(i); - const bc = b.charCodeAt(i); - if (!ac && !bc) return 0; - if (ac && !bc) return 1; - if (!ac && bc) return -1; - if (ac > bc) return 1; - if (ac < bc) return -1; - } -}; - -const sortedLocations = locations => [...locations].sort(nestedSort); - -const memoizedSortedLocations = weakMemoizeArray(sortedLocations); +const getterHelper = ['','']; const proxyShallowEqual = (a, b, locations) => { DISABLE_ALL_PROXIES = true; + const differ = []; + differs = []; const ret = (() => { - differs = []; - let valuables = null; - const nestedLocations = memoizedSortedLocations(locations); - let lastEqualKey = ''; - for (let i = 0; i < nestedLocations.length; ++i) { - const key = nestedLocations[i]; - if ( - lastEqualKey && - key.length > lastEqualKey.length && - key[lastEqualKey.length] === '.' && - key.indexOf(lastEqualKey) === 0 - ) { - continue; + const root = memoizedBuildTrie(locations); + const walk = (la, lb, node) => { + if (la === lb || deepDeproxify(la) === deepDeproxify(lb)) { + return true; } - - const path = key.split('.'); - const la = get(a, path); - const lb = get(b, path); - - if ((la === lb) || (deepDeproxify(la) === deepDeproxify(lb))) { - lastEqualKey = key; - } else { - if (!valuables) { - valuables = memoizedCollectValuables(locations); - } - if (valuables.indexOf(key) >= 0) { - differs.push([key, 'not equal']); + if (node === EDGE) { + return false; + } + const items = Object.keys(node); + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + getterHelper[1]=item; + if (!walk( + get(la, getterHelper), + get(lb, getterHelper), + node[item], + )) { + differ.unshift(item); return false; } } - } - - return true; + return true; + }; + return walk(a, b, root); })(); DISABLE_ALL_PROXIES = false; + if(!ret) { + differ.unshift(''); + differs.push([differ.join('.'), 'not equal']); + } return ret; }; + const proxyEqual = (a, b, affected) => { differs = []; return proxyCompare(a, b, memoizedCollectValuables(affected)); diff --git a/src/objectTrie.js b/src/objectTrie.js new file mode 100644 index 0000000..e088a88 --- /dev/null +++ b/src/objectTrie.js @@ -0,0 +1,23 @@ +import {weakMemoizeArray} from "./weakMemoize"; + +export const EDGE = 'EDGE'; + +const buildObjTrie = (lines) => { + const root = {}; + for (let i = 0; i < lines.length; ++i) { + const path = lines[i].split('.'); + let node = root; + const lastIndex = path.length - 1; + for (let j = 1; j < lastIndex; ++j) { + const item = path[j]; + if (!node[item] || node[item] === EDGE) { + node[item] = {}; + } + node = node[item]; + } + node[path[lastIndex]] = EDGE; + } + return root; // FIXME +}; + +export const memoizedBuildTrie = weakMemoizeArray(buildObjTrie); \ No newline at end of file