What happened
Running a set of simple benchmarks, the Perry-compiled binary's RSS grew to 1432MB — 4.5x more than Node (315MB) and 3.7x more than tsx (386MB). Heap usage peaked at 319MB vs 90MB (node) / 166MB (tsx).
More concerning: RSS kept growing monotonically across benchmarks even when heap was stable, suggesting either a leak or an unbounded arena allocator.
What you expected
A natively compiled binary should use less or similar memory to a JIT runtime, not 4.5x more. The gap between heapUsed and RSS (319MB heap → 1432MB RSS) also seems abnormally large.
Minimal reproduction
const ITERATIONS = 100;
const N = 100_000;
function bench(name: string, fn: () => void, iters: number) {
for (let i = 0; i < Math.min(10, Math.floor(iters / 10)); i++) fn();
const start = performance.now();
for (let i = 0; i < iters; i++) fn();
const mem = process.memoryUsage();
console.log(`[${name}] rss=${(mem.rss / 1024 / 1024).toFixed(0)}MB | heap=${(mem.heapUsed / 1024 / 1024).toFixed(0)}MB`);
}
bench('array-sort-100k', () => {
const arr = new Array<number>(N);
for (let i = 0; i < N; i++) arr[i] = Math.random();
arr.sort((a, b) => a - b);
}, ITERATIONS);
bench('filter-map-reduce-100k', () => {
const arr = new Array<number>(N);
for (let i = 0; i < N; i++) arr[i] = i;
arr.filter(x => x % 2 === 0).map(x => x * x).reduce((acc, x) => acc + x, 0);
}, ITERATIONS * 2);
bench('map-set-get-100k', () => {
const m = new Map<number, number>();
for (let i = 0; i < N; i++) m.set(i, i * 2);
let sum = 0;
for (let i = 0; i < N; i++) sum += m.get(i) ?? 0;
return sum;
}, ITERATIONS * 2);
bench('set-intersection-50k', () => {
const half = N / 2;
const a = new Set<number>();
const b = new Set<number>();
for (let i = 0; i < half; i++) { a.add(i); b.add(i + half); }
const intersection = new Set<number>();
for (const x of a) { if (b.has(x)) intersection.add(x); }
return intersection.size;
}, ITERATIONS);
bench('string-concat-10k', () => {
let s = '';
for (let i = 0; i < 10_000; i++) s += 'a' + i.toString();
}, ITERATIONS);
const obj = { a: 1, b: 'foo', c: [1, 2, 3], d: { x: true } };
const jsonStr = JSON.stringify(obj);
bench('json-stringify-100', () => { for (let i = 0; i < 100; i++) JSON.stringify(obj); }, ITERATIONS * 10);
bench('json-parse-100', () => { for (let i = 0; i < 100; i++) JSON.parse(jsonStr); }, ITERATIONS * 10);
Command you ran:
perry compile benchmark.ts -o dist/benchmark-perry
./dist/benchmark-perry
Environment
- Perry version: 0.5.1220
- Host OS: linux
- Target: native
- Installed via: (from path
/usr/local/bin/perry)
Diagnostic output
Perry RSS progression across benchmarks (cumulative, same process):
[array-sort-100k] rss=39MB heap=22MB
[filter-map-reduce] rss=61MB heap=16MB ← heap drops but RSS grows
[map-set-get] rss=821MB heap=16MB ← huge RSS jump
[set-intersection] rss=1138MB heap=16MB ← heap flat, RSS +300MB
[string-concat] rss=1223MB heap=106MB
[json-stringify] rss=1396MB heap=287MB
[json-parse] rss=1433MB heap=319MB
Node.js baseline (same binary, same workload):
RSS 峰值: 315MB | Heap 峰值: 90MB
tsx baseline:
RSS 峰值: 386MB | Heap 峰值: 166MB
Anything else
Key observations:
- RSS spikes from 61MB → 821MB after
map-set-get-100k (Map with 100k entries, ITERATIONS=200). This is suspicious.
- Heap stays at 16MB during the spike — the large RSS isn't reflected in heapUsed, suggesting allocations outside the GC-managed heap (direct native allocs, arenas, or mmap'd regions not tracked by heap metrics).
- RSS never decreases across the process lifetime — no page reclamation even after temporary allocations should have been freed.
What happened
Running a set of simple benchmarks, the Perry-compiled binary's RSS grew to 1432MB — 4.5x more than Node (315MB) and 3.7x more than tsx (386MB). Heap usage peaked at 319MB vs 90MB (node) / 166MB (tsx).
More concerning: RSS kept growing monotonically across benchmarks even when heap was stable, suggesting either a leak or an unbounded arena allocator.
What you expected
A natively compiled binary should use less or similar memory to a JIT runtime, not 4.5x more. The gap between heapUsed and RSS (319MB heap → 1432MB RSS) also seems abnormally large.
Minimal reproduction
Command you ran:
Environment
/usr/local/bin/perry)Diagnostic output
Perry RSS progression across benchmarks (cumulative, same process):
Node.js baseline (same binary, same workload):
tsx baseline:
Anything else
Key observations:
map-set-get-100k(Map with 100k entries, ITERATIONS=200). This is suspicious.