Skip to content

perf: fast path len identifier calls#136

Merged
joshcramer merged 2 commits into
mainfrom
perf/len-identifier-fastpath
Jun 23, 2026
Merged

perf: fast path len identifier calls#136
joshcramer merged 2 commits into
mainfrom
perf/len-identifier-fastpath

Conversation

@larimonious

Copy link
Copy Markdown
Contributor

Summary

  • Adds a targeted interpreter fast path for len(identifier) so large arrays/maps are not cloned just to compute length.
  • Keeps normal len shadowing behavior intact; user-defined len still wins.
  • Adds a /native/calls benchmark fixture and records the DD-061 PR 5 result/recommendation update.

Benchmark

Local 3s/3-run wrk pass at 16 connections / 2 threads, comparing origin/main plus the new benchmark fixture to this branch's dev-release binary:

  • /native/calls: 349.95 -> 1574.70 RPS (+350.0%)
  • other routes stayed within normal local noise

Verification

  • cargo fmt
  • cargo build --profile dev-release
  • cargo test
  • ./target/dev-release/ntnt docs --generate
  • cargo fmt -- --check
  • git diff --check
  • ./target/dev-release/ntnt lint examples/perf/server.tnt — 0 errors / 0 warnings; existing benchmark-file contract suggestions only
  • focused rerun after final simplification: cargo test fast_path --lib, cargo test environment_get --lib

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a targeted fast path for len(identifier) calls in the interpreter, avoiding unnecessary cloning of large arrays and maps just to compute their length, while preserving full user-defined shadowing semantics. It also converts Environment::get from recursive to iterative to prevent stack overflow on deep scope chains.

  • Fast path (src/interpreter.rs): Before the normal call dispatch, if the callee is len, the sole argument is an identifier, and len currently resolves to the builtin NativeFunction, len_of_binding is called directly on the environment chain — identical logic to the builtin, no clone. Falls through to the normal path for any expression argument or when len is shadowed.
  • Iterative get: The existing recursive Environment::get is rewritten as an iterative loop with a 64-depth stress test added to guard against regression.
  • Benchmark fixture: A new /native/calls route and benchmark entry exercise the fast path in a 5000-iteration loop, recording a +350% RPS gain on local hardware.

Confidence Score: 5/5

Safe to merge; the fast path is strictly additive and falls through to the existing normal dispatch on any non-matching case.

The fast path's len_of_value is byte-for-byte identical to the existing builtin's match arms. Shadowing, type errors, and parent-scope traversal are all covered by new unit tests. The iterative get rewrite is tested with a 64-level depth chain. No behavioral changes are possible for callers that do not hit the narrow len(identifier) pattern.

No files require special attention.

Important Files Changed

Filename Overview
src/interpreter.rs Adds iterative get, len_of_binding, and len_of_value to Environment, and inserts a fast path for len(identifier) in expression evaluation; fast-path logic matches the existing builtin exactly; tests cover shadowing, type errors, and grandparent-scope traversal.
examples/perf/server.tnt Adds /native/calls benchmark route that exercises len(identifier) in a 5000-iteration loop across string, array, and expression arguments.
scripts/bench/run-benchmarks.py Registers the new /native/calls endpoint in the benchmark suite; mechanical, low-risk change.
examples/perf/README.md Documents the new /native/calls route in the benchmark fixture table.
design-docs/dd-061-interpreter-performance-roadmap.md Updates PR 5 scope from broad native-call fast path to targeted len(identifier) fast path, marks acceptance criteria complete, and records benchmark results.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Expression::Call evaluated"] --> B{"function is Identifier?"}
    B -- No --> Z["Normal call dispatch"]
    B -- Yes --> C{"name is len AND 1 argument?"}
    C -- No --> D{"name is old?"}
    D -- Yes --> OLD["old() postcondition handling"]
    D -- No --> Z
    C -- Yes --> E{"argument is Identifier?"}
    E -- No --> Z
    E -- Yes --> F["borrow env, get len"]
    F --> G{"resolves to NativeFunction named len?"}
    G -- No --> Z
    G -- Yes --> H["borrow env, len_of_binding"]
    H --> I{"binding found?"}
    I -- No --> Z
    I -- Yes --> J["len_of_value without clone"]
    J --> K{"value type?"}
    K -- String --> L["Int s.len()"]
    K -- Array --> M["Int a.len()"]
    K -- Map --> N["Int m.len()"]
    K -- Other --> O["TypeError"]
    Z --> P["eval function expr, clone arg, NativeFunction dispatch"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["Expression::Call evaluated"] --> B{"function is Identifier?"}
    B -- No --> Z["Normal call dispatch"]
    B -- Yes --> C{"name is len AND 1 argument?"}
    C -- No --> D{"name is old?"}
    D -- Yes --> OLD["old() postcondition handling"]
    D -- No --> Z
    C -- Yes --> E{"argument is Identifier?"}
    E -- No --> Z
    E -- Yes --> F["borrow env, get len"]
    F --> G{"resolves to NativeFunction named len?"}
    G -- No --> Z
    G -- Yes --> H["borrow env, len_of_binding"]
    H --> I{"binding found?"}
    I -- No --> Z
    I -- Yes --> J["len_of_value without clone"]
    J --> K{"value type?"}
    K -- String --> L["Int s.len()"]
    K -- Array --> M["Int a.len()"]
    K -- Map --> N["Int m.len()"]
    K -- Other --> O["TypeError"]
    Z --> P["eval function expr, clone arg, NativeFunction dispatch"]
Loading

Reviews (2): Last reviewed commit: "test: cover len fast path parent scopes" | Re-trigger Greptile

Comment thread src/interpreter.rs
@joshcramer joshcramer merged commit b598368 into main Jun 23, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants