diff --git a/.changeset/globalcache-preallocate.md b/.changeset/globalcache-preallocate.md new file mode 100644 index 000000000000..06bc3b4cd58e --- /dev/null +++ b/.changeset/globalcache-preallocate.md @@ -0,0 +1,7 @@ +--- +'@data-client/normalizr': patch +--- + +Improve denormalization performance by pre-allocating the dependency tracking slot + +Replace `Array.prototype.unshift()` in `GlobalCache.getResults()` with a pre-allocated slot at index 0, avoiding O(n) element shifting on every cache-miss denormalization. diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 85ffe0df16a6..8b62dce07210 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -6,8 +6,13 @@ import type { INVALID } from '../denormalize/symbol.js'; import type { EntityInterface, EntityPath } from '../interface.js'; import type { DenormGetEntity } from './types.js'; +const PLACEHOLDER_DEP: Dep = { + path: { key: '', pk: '' }, + entity: undefined, +}; + export default class GlobalCache implements Cache { - private dependencies: Dep[] = []; + private dependencies: Dep[] = [PLACEHOLDER_DEP]; private cycleCache: Map> = new Map(); private cycleIndex = -1; private localCache: Map> = new Map(); @@ -120,10 +125,10 @@ export default class GlobalCache implements Cache { if (paths === undefined) { data = computeValue(); - // we want to do this before we add our 'input' entry + // we want to do this before we fill our 'input' entry paths = this.paths(); - // for the first entry, `path` is ignored so empty members is fine - this.dependencies.unshift({ path: { key: '', pk: '' }, entity: input }); + // fill pre-allocated slot 0 with the input reference + this.dependencies[0] = { path: { key: '', pk: '' }, entity: input }; this._resultCache.set(this.dependencies, data); } else { paths.shift(); @@ -132,7 +137,12 @@ export default class GlobalCache implements Cache { } protected paths() { - return this.dependencies.map(dep => dep.path); + const deps = this.dependencies; + const paths = new Array(deps.length - 1); + for (let i = 1; i < deps.length; i++) { + paths[i - 1] = deps[i].path; + } + return paths; } }