# Performance tuning

> The Elide settings that change speed and memory: serving contexts, network transport, heap size and profiles, plus how to benchmark startup and serving.

This page lists the settings that change Elide's speed and memory use, and how to measure them.

| Setting | Applies to | Default |
| --- | --- | --- |
| `--contexts=N`, `--contexts=auto` | Fetch-handler servers under `run`, `serve`, `dev` (Linux for more than one) | `1` |
| `engine.contexts` in `elide.pkl` | `elide serve`, `elide dev` | `1` |
| `--max-mem=SIZE` | Every command | `4g` for one-shot runs; sized from host memory for `serve`, `dev` |
| `--transport=VALUE`, `ELIDE_TRANSPORT_V2` | `fetch` and dependency downloads | `auto` |
| `--sandbox=PROFILE` | Every program | `trusted` |

## Add serving contexts

A context is one copy of your app with its own module state. Each context runs requests on its own thread, so more
contexts serve more requests at once on more cores.

```bash
elide serve --no-tui --contexts=4 server.mjs
elide run --contexts=auto server.mjs
```

`auto` uses the physical cores minus two. Set it per project for `serve` and `dev`:

```pkl
// elide.pkl
amends "elide:project.pkl"

engine {
  contexts { spawn = "auto"; reserve = 2; min = 1; max = 16 }
}
```

| Rule | Detail |
| --- | --- |
| `elide run` | Reads `--contexts` only; pass the flag. |
| Module state | Not shared between contexts; keep shared state in a database or another service. |
| `node:http` servers | One context; a second copy fails with `EADDRINUSE`. |
| Platforms | More than one context requires Linux. |

Server I/O threads follow the context count; `ELIDE_HTTP_NATIVE_WORKERS` does not change them under `run`, `serve` or
`dev`. Field reference: [Engine contexts](https://elide.help/docs/elide-pkl-reference#engine-contexts); walkthrough:
[Several contexts](https://elide.help/docs/http-service#several-contexts).

## Cap memory

```bash
elide --max-mem=512m run app.js
elide --max-mem=2g serve server.mjs
```

`--max-mem` caps the heap. One-shot runs (`run`, a bare file) default to at most `4g`; `serve` and `dev` size the heap
from host or container memory, so set it on every container or shared host. A full heap exits `1` with
`the heap address space is exhausted`; a cap of `128m` or less fails at startup. See
[Troubleshooting](https://elide.help/docs/troubleshooting).

## Choose a network transport

`--transport` picks how `fetch` and dependency downloads reach the network. It does not change HTTP servers.

| Value | Use |
| --- | --- |
| `auto` | Best transport for the host. On Linux this is `v2` with epoll. |
| `v2` | Elide's own transport, with its own TLS. `ELIDE_TRANSPORT_V2=1` selects it when no flag is given. |
| `epoll`, `io_uring` | `v2` with that Linux event driver. |
| `kqueue` | `v2` on macOS; other platforms exit `2`. |
| `nio` | Portable transport, for comparison or when a native driver fails. |

```bash
elide --transport=io_uring run client.mjs
elide info
```

`elide info` prints the selected transport and driver on its `Transport:` line. An explicit `--transport` wins over
`ELIDE_TRANSPORT_V2`. `ELIDE_TRANSPORT_DIAGNOSTICS=1` prints transport statistics to stderr.

## Pick an execution profile

| Profile | Status |
| --- | --- |
| `--sandbox=trusted` | Default; no confinement cost. |
| `--sandbox=sandboxed` | Exits `2`: `--sandbox=sandboxed is unavailable` |
| `--sandbox=isolated` | Exits `2`: `--sandbox=isolated is not available in this build` |

Permission flags and limits are on [Sandbox and Limits](https://elide.help/docs/sandbox).

## Find the slow part

| Question | Tool |
| --- | --- |
| Where does startup time go? | `ELIDE_DIAG_STARTUP=1 elide run app.js` prints each startup phase with a timestamp to stderr. |
| Which functions run most? | `elide run --profiler=cputracing app.js`; see [Debugging](https://elide.help/docs/debugging#profile-cpu-time). |
| What happened during a run or a request? | `elide --jfr=run.jfr run app.js`; see [Monitoring and Profiling](https://elide.help/docs/instruments). |
| Where does compiled code spend CPU time? | `--jitdump=DIR` with Linux `perf`; see [Monitoring and Profiling](https://elide.help/docs/instruments). |
| Which requests are slow in production? | Server spans and `http.server.request.duration`; see [OpenTelemetry](https://elide.help/docs/opentelemetry/runtime). |

## Measure startup

Run [hyperfine](https://github.com/sharkdp/hyperfine) with `-N` (no shell) so each sample is one process:

```javascript
// hello.js
console.log("Hello, world!");
```

```bash
hyperfine -N --warmup 10 --runs 100 --export-json startup.json \
  'elide run hello.js' 'node hello.js' 'bun hello.js' 'deno run hello.js'
```

Call each runtime's real binary: a version-manager shim or wrapper script adds a process to every sample. Give the full
path if `which elide` is not the installed binary.

## Measure serving

Serve one fixed response and load it with [wrk](https://github.com/wg/wrk):

```javascript
// server.mjs
export default {
  hostname: "127.0.0.1",
  port: 3000,
  fetch() {
    return new Response("Hello, world!");
  },
};
```

```bash
elide run server.mjs &
wrk -t12 -c256 -d5s http://127.0.0.1:3000/ > /dev/null
wrk -t12 -c256 -d10s --latency http://127.0.0.1:3000/ > elide.txt
kill %1
```

| Step | Why |
| --- | --- |
| Discard a 5-second run first | The first requests compile the handler. |
| Pin the server and `wrk` to separate cores, e.g. `taskset -c 0-7 elide run server.mjs` and `taskset -c 8-15 wrk ...` | Keeps the load generator from taking the server's CPU. |
| Repeat with `--contexts=auto` | One context uses one thread for your handler. |
| Compare `Requests/sec` and the `--latency` percentiles | Averages hide tail latency. |

`bun server.mjs` serves the same file; `deno serve --port 3000 --host 127.0.0.1 server.mjs` needs the address on the
command line.

## Multi-context serving

```sh
elide run --contexts=auto server.mjs
# Or choose the count explicitly:
elide run --contexts=24 server.mjs
```

One process evaluates the server entry module in each context and serves requests concurrently.
Each context has its own JavaScript globals and module state. The process shares one managed heap,
garbage collector, and eligible parsed/JIT-compiled code across contexts. Ordinary objects are not shared
application state; use external coordination when replicas must agree. Process failures and GC pauses
can affect all contexts. Start with `auto`, then measure your workload before choosing a fixed count.

The [published HTTP benchmarks](https://elide.help/docs/benchmarks#multi-context-peak-serving) distinguish default single-context
serving from tuned multi-context serving with HTTP pipelining, and include a configuration table,
architecture diagram, hardware/OS details, and a downloadable runner. Their peak configuration also
selects inline scheduling and io_uring; `--contexts` alone does not reproduce it.


## See also

- [Compatibility Matrix](https://elide.help/docs/compatibility) for API coverage per release
- [Changelog](https://elide.help/docs/changelog) for performance changes per release
