Skip to content

Too many object allocations when booting an empty app #20870

Open
@NullVoxPopuli

Description

@NullVoxPopuli
code I stole and adapted from warp-drive
  globalThis.stats = {
    __primitiveInstanceId: 0,
    __data: globalThis.stats?.__data || {},
    resetMetricCounts() {
      globalThis.stats.__data = {};
    },
    getMetricCounts() {
      return structuredClone(globalThis.stats.__data);
    },
    summary() {
      let stats = globalThis.stats.getMetricCounts();

      for (let [k, v] of Object.entries(stats).sort((a, b) => a[0].localeCompare(b[0]))) {
        if (k.includes(' ')) continue;

        console.log(`${k}: ${v}`);
      }
    }
  }

  function interceptAndLog(klassName, methodName) {
    const klass = globalThis[klassName];

    if (methodName === 'constructor') {
      const instantiationLabel = `new ${klassName}()`;
      globalThis[klassName] = class extends klass {
        constructor(...args) {
          super(...args);

          const instanceId = globalThis.stats.__primitiveInstanceId++;
          globalThis.stats.__data[instantiationLabel] =
            (globalThis.stats.__data[instantiationLabel] || 0) + 1;
          this.instanceName = `${klassName}:${instanceId} - ${new Error().stack?.split('\n')[2]}`;
        }
      };
    } else {
      const original = klass.prototype[methodName];
      const logName = `${klassName}.${methodName}`;

      klass.prototype[methodName] = function (...args) {
        globalThis.stats.__data[logName] = (globalThis.stats.__data[logName] || 0) + 1;
        const { instanceName } = this;
        if (!instanceName) {
          const instanceId = globalThis.stats.__primitiveInstanceId++;
          this.instanceName = `${klassName}.${methodName}:${instanceId} - ${new Error().stack?.split('\n')[2]}`;
        }
        const instanceLogName = `${logName} (${instanceName})`;
        globalThis.stats.__data[instanceLogName] =
          (globalThis.stats.__data[instanceLogName] || 0) + 1;
        return original.apply(this, args);
      };
    }
  }

  interceptAndLog('Set', 'constructor');
  interceptAndLog('Set', 'add');
  interceptAndLog('Set', 'delete');
  interceptAndLog('Set', 'has');
  interceptAndLog('Set', 'set');
  interceptAndLog('Set', 'get');

  interceptAndLog('Map', 'constructor');
  interceptAndLog('Map', 'set');
  interceptAndLog('Map', 'delete');
  interceptAndLog('Map', 'has');
  interceptAndLog('Map', 'add');
  interceptAndLog('Map', 'get');

  interceptAndLog('WeakSet', 'constructor');
  interceptAndLog('WeakSet', 'add');
  interceptAndLog('WeakSet', 'delete');
  interceptAndLog('WeakSet', 'has');
  interceptAndLog('WeakSet', 'set');
  interceptAndLog('WeakSet', 'get');

  interceptAndLog('WeakMap', 'constructor');
  interceptAndLog('WeakMap', 'set');
  interceptAndLog('WeakMap', 'delete');
  interceptAndLog('WeakMap', 'has');
  interceptAndLog('WeakMap', 'add');
  interceptAndLog('WeakMap', 'get');
    

Results from: embroider-build/embroider#2339

> stats.summary()

Map.delete: 2
Map.get: 606
Map.has: 161
Map.set: 190
Set.add: 182
Set.delete: 30
Set.has: 132
WeakMap.get: 1417
WeakMap.has: 226
WeakMap.set: 301
WeakSet.add: 146
WeakSet.has: 193

Repro:

  • new app
  • make a <script> tag in the <head> of your HTML
  • paste the above code snippet
  • visit /
  • open the console
  • run stats.summary()
  • also: there is stats.getMetricCounts() to see some specific paths

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions