Skip to content

Laravel follow-up: RSS parity, per-request leaks, XPASS promotion, real-code throughput #600

Open

Description

@blacktrs

Goal

Laravel example (examples/laravel, PR #564 merged as cac708f9) serves requests natively. What's left: memory footprint, per-request leaks, and throughput on real framework code. Target is Zend parity or better on all three. Zend is the oracle for both behaviour and the baseline numbers.

Baseline (2026-11-08, macOS arm64, -O2, pool on)

Metric Manticore Zend 8.5
RSS under load 200–310 MB (boot 154 MB) ~47 MB
Leak, GET /ping ~1.2 KB/req —
Leak, GET /hello ~3 KB/req 0.7 KB/req (SESSION_DRIVER=array)
Leak, GET /api/notes ~1.5 KB/req (RSS flat) —
Instr/req ping / hello / notes 1.87M / 3.22M / 127M Laravel-side ratio ≈ 1.34–1.5× of Zend

Measure with tools/prof/serve.sh + report.php (see docs/profiling.md).

Scope

1. Deferred free of buffered rc=0 objects (RSS)

An object whose refcount drops to 0 while it is in the cycle-collector root buffer is marked dead (rc word 0x84…) but is freed only on the next collection. The threshold is 10 000 roots, so whole dead graphs stay resident. Zend frees right away and unlinks the node from the buffer.

  • Free on rc→0 even when buffered: unlink from the root buffer, or tombstone the slot.
  • Done when: RSS under wrk is within 2× of Zend, and the self-host fixpoint is unchanged.

2. Per-request leak on /ping (~1.2 KB/req)

Known roots from hello: closures of $x ?? fn, setRouteResolver(fn), Str::random, hash_hmac/CRYPTO, arrays via __mir_realloc_tagged. notes is not broken down yet.

  • Each root gets a minimal repro in tests/aot/repro/leak/ plus an issue (tools/file_bug.sh), then a fix.
  • Check at -O0 without pool too: -O2 deletes unread mallocs and hides leaks.
  • Done when: ping ≤ Zend's per-request growth.

3. #590 and #592

4. Promote 8 XPASS repros

bash tests/aot/xfail.sh reports 8 XPASS (fixed as a side effect of the Laravel work). For each one: git mv the repro to tests/aot/cases/ and its .expected to tests/aot/expected/<name>.out, drop the // issue: line, close its issue (Fixes #N).

5. Throughput on real PHP code (profile first, then fix)

Profile ping/hello/notes by instruction count (Linux perf as well as macOS) and attack the top hotspots. Known so far: refcount + malloc ≈ 20%, __mir_array_index_find ≈ 5% (now alwaysinline). Candidates to confirm or drop by measurement:

  • Retain/release pair elision: borrowed params, temporaries passed straight through, foreach values.
  • Dynamic dispatch: $obj->$method(...$args), __call/__callStatic, call_user_func_array, static:: LSB. Laravel facades and Eloquent forwarding go through these on every call. Try a per-call-site inline cache keyed by class id plus method name.
  • Constant-key array access: precompute the hash of a literal string key at compile time and skip rehashing on $a['key']. Config/container arrays are the hot case.
  • Dynamic property access: name lookup on erased objects / stdClass (Eloquent attributes, PDO::FETCH_OBJ).
  • Closure allocation: env boxing for short-lived fn passed to tap/collect()->map etc.
  • String building: concat arena / sprintf / implode on view rendering paths.

Each candidate ships with a microbench and a before/after instr/req. No change lands without a measured win.

  • Done when: Laravel instr/req ≤ Zend on ping and notes, and symfony-demo shows no regression.

Rules

  • Branch per item off local main. PR per item, never push to main.
  • Linux gate required for anything that touches src/Runtime/, the allocator or the cycle collector.
  • Full AOT suite + difftest before merge (run by the maintainer).

Activity

  1. added
    enhancementNew feature or request
    leakMemory that php releases and the native binary does not
    area:runtimeRuntime: shutdown, traces, collector
    on Nov 8, 2026
  2. blacktrs commented on Nov 8, 2026

    @blacktrs
    ContributorAuthor

    Measured on 2026-11-08 (macOS arm64, -O2, WORKERS=00, /api/ping). The RSS numbers in item 1 were misleading:

    RSS footprint (dirty anon)
    Manticore, after boot 131 MB 8 MB
    Manticore, after ~52K requests (wrk 10 s, 5.2K req/s) 199 MB 75 MB
    Zend php -S 49–53 MB 31–35 MB
    • Most of the RSS is clean, file-backed text pages of the 713 MB binary. That is not heap.
    • The heap grows ~67 MB over 52K requests, about 1.3 KB/req. This is the item 2 leak, not delayed freeing.
    • Item 1 is already mostly in place: __manticore_cc_drop_dead drops a buffered rc=0 object immediately, and only a ~100 B shell waits in the root buffer.

    Revised plan: item 2 (per-request leak) is the RSS lever. Item 1 shrinks to "unlink the dead shell from the root buffer", low priority.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:runtimeRuntime: shutdown, traces, collectorenhancementNew feature or requestleakMemory that php releases and the native binary does not

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions