Files
oxmc 8a8a824f3b Initial commit: tiered Chromium/V8 flags, GC/cache monitor, per-window memory helpers
Extracted and generalized from hand-tuned memory optimizations in an Electron app
template: memory/GPU/Raspberry-Pi-tiered command-line flags, a runtime GC + total-
process memory monitor with pressure-triggered deep clean, and per-window hide/
minimize memory helpers.
2026-07-26 03:10:19 -07:00

132 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# electron-performance
Reduce Electron's memory/CPU footprint: tiered Chromium/V8 command-line flags (memory-,
GPU-, and Raspberry-Pi-generation-aware), a runtime GC/cache monitor with total-process
memory tracking, and per-window memory helpers.
CommonJS output, written in TypeScript (ships `.d.ts`).
## Install
```sh
npm install electron-performance
```
`@oxmc/node-gpuinfo` is an optional native dependency used for GPU-tier detection; if it
fails to install on a given platform/arch, GPU detection just degrades to an empty list
(flags fall back to the "no GPU" branch) instead of breaking the install.
## Usage
Three independent pieces. Wire in whichever you need.
### 1. Command-line flags — call before `app.whenReady()`
```js
const { app } = require('electron');
const { applyCommandLineFlags } = require('electron-performance');
// Must run before app is ready — Chromium ignores switches set later.
const system = applyCommandLineFlags(app);
```
Disable specific categories, or override the computed heap/cache sizes:
```js
applyCommandLineFlags(app, {
enable: { spellcheck: false, notifications: false }, // leave these Chromium defaults alone
jsHeapSizeMB: 1024, // override the auto-tiered --max-old-space-size
});
```
`computeFlags(system, options)` is the pure version (no `app`, just returns the flag
list) if you want to inspect/test the decision before applying it:
```js
const { getSystemInfo, computeFlags } = require('electron-performance');
const system = getSystemInfo();
console.log(computeFlags(system));
```
### 2. Runtime memory monitor — call after `app.whenReady()`
Periodic + pressure-triggered GC, periodic session-cache clearing, and a pressure-triggered
**deep clean**: when total RSS across *every* process (main, renderers, GPU, utility — via
`app.getAppMetrics()`, not just main's own heap) crosses a threshold, it wipes service
worker/cache-storage data on every window's session and forces GC. That total-memory check
matters — for a multi-window or webview-style app, almost all memory lives in renderer
processes, which `process.memoryUsage()` can't see at all.
```js
const { app, BrowserWindow } = require('electron');
const { startMemoryMonitor } = require('electron-performance');
app.whenReady().then(() => {
const monitor = startMemoryMonitor({ app, BrowserWindow }, { system }); // reuse the SystemInfo from step 1
// monitor.stop() to tear down
// monitor.forceGc() / monitor.deepClean() to run either immediately
// monitor.getMemoryUsage() / monitor.getTotalMemoryUsage() to inspect on demand
});
```
Omit `BrowserWindow` (with `clearCache: false, deepClean: false`) if you only want the GC loop.
### 3. Per-window helpers
```js
const { getRecommendedWebPreferences, attachWindowMemoryOptimizations } = require('electron-performance');
const win = new BrowserWindow({
webPreferences: getRecommendedWebPreferences({ preload: '...' }),
});
attachWindowMemoryOptimizations(win, {
onSettleHidden: () => monitor.forceGc(), // clears this window's cache ~15s after hide/minimize, cancels if shown again
muteAudioOnHide: true, // opt-in: mute audio/video while hidden, unmute on show
});
```
## Tuning tiers
- **Memory tier**: `< 4096MB` low, `< 8192MB` medium, else high — drives `--max-old-space-size`,
`--max-semi-space-size`, disk cache size, and renderer process limits.
- **GPU tier**: discrete GPU present / integrated-only / none — drives GPU-acceleration
flags (`@oxmc/node-gpuinfo`, filtered for software/virtual renderers).
- **Raspberry Pi**: detected via `detect-rpi`; generations 13 get aggressive tuning
(GPU disabled, tiny heap), gen 4 moderate, gen 5 light — this branch takes priority
over the generic memory/GPU tiering above.
All of the above can be disabled per-category via `options.enable` (see `FlagCategories`
in `types.ts`), or bypassed entirely by passing your own `SystemInfo`.
## Notable design decisions
- **`keepBackgroundActive` defaults to off** (opt-in). It's tempting to disable
Chromium's renderer backgrounding/timer-throttling for snappier background windows, but
that undoes one of the cheapest, biggest memory/CPU wins Chromium gives you for free:
letting hidden/minimized windows' renderers actually idle. Only set
`enable: { keepBackgroundActive: true }` if you need background windows to stay fully
responsive (e.g. continuous audio/streaming).
- **`enable-features`/`disable-features` merge instead of clobber.** Chromium's
`commandLine.appendSwitch(name, value)` overwrites a switch if called more than once
with the same name rather than merging comma-lists — call it from two different tuning
branches and the second silently drops the first. `computeFlags` merges repeated
`enable-features`/`disable-features` calls into one comma-joined switch internally so
nothing gets lost.
- **`global.gc` actually works in the main process.** `--js-flags=--expose-gc` set via
`commandLine.appendSwitch` only reaches renderer/utility processes — Electron's main
process already has V8 initialized by the time app code runs, so that switch alone never
gives main a working `global.gc`. `startMemoryMonitor` self-enables it via
`v8.setFlagsFromString('--expose-gc')` + `vm.runInNewContext('gc')` instead. Exposed
standalone as `enableMainProcessGc()`, and toggleable via `{ exposeGc: false }`.
- **Deep clean is conservative by default.** `deepCleanStorages` defaults to
`['serviceworkers', 'cachestorage', 'shadercache']` — memory-heavy but not user session
state — deliberately excluding `cookies`/`localstorage`/`indexdb` so a deep clean can't
silently log a user out of a page they're viewing. Pass your own list for a more
aggressive clean if that tradeoff is acceptable for your app.
- **`disableBackForwardCache`** (default `'auto'`, low memory tier only) turns off BFCache,
which otherwise keeps a fully-frozen copy of every recently-navigated-away-from page
resident in memory for instant back/forward.
- **`rendererProcessLimit`** caps `--renderer-process-limit` by memory tier — matters most
for apps that open many BrowserWindows/WebContentsViews, each normally its own process.