ember.js icon indicating copy to clipboard operation
ember.js copied to clipboard

Too many object allocations when booting an empty app

Open NullVoxPopuli opened this issue 10 months ago • 2 comments

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: https://github.com/embroider-build/embroider/pull/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

NullVoxPopuli avatar Mar 13 '25 20:03 NullVoxPopuli

Hi I’m interested in contributing to this issue. Could you please assign it to me? Thank you!

Suchi1905 avatar Oct 24 '25 14:10 Suchi1905

no need for that, you can just pick up the work <3

also, good luck.

NullVoxPopuli avatar Oct 24 '25 14:10 NullVoxPopuli