Skip to content

Too many object allocations when booting an empty app #20870

@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

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions