# Get maximum performance

> A practical checklist for writing Alice that compiles to the fastest .NET code: numeric types, loops, arrays, ownership, parallelism, the JIT, and the GC.

Canonical: https://alice-lang.org/documentation/guides/max-performance/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

Alice compiles to C# and then to .NET IL, so its fast code is the code .NET runs fast. Most of the work happens without annotations: the compiler fuses pipelines into loops, removes overflow checks it can prove dead, keeps temporaries off the heap, and parallelizes what it can prove independent. This guide is about writing source that gives the compiler those proofs, and about the few explicit tools for when it cannot find them.

Measure with `alice build --release` and a fresh process, and inspect with `alice emit program.al`. See [understand performance](https://alice-lang.org/documentation/guides/performance.md) for what each transformation looks like.

## Pick numeric types deliberately

Integer `+ - *` raise `OverflowException` on overflow (since 0.12). The compiler drops the check whenever value ranges prove it cannot fire: counted-loop counters and `i ± k` offsets bounded by `.size`, masked values (`x & 0xFFFF`), widened `Int32` products, and shift counts, which are modular. Everything else keeps one compare-and-branch per operation.

- Use `Int32` for indices and counters, `Int64` only where values need it.
- For hashing, PRNGs and checksums, where wraparound is the intent, use `&+ &- &*` or a wrapping region:

```alice
h = 0
unchecked
  for c in s.chars
    h = h * 31 + c.ord
  end
end
```

- `x / 8` and `x % 16` become a shift and a mask (floored semantics are preserved). `2.0 ** n` and `x ** 2` lower to exact cheap forms.

## Write counted loops over arrays

`for i in 0...xs.size` with an array index gives the JIT a loop it can bounds-check once and the compiler a counter it can reason about. Iterator chains with literal blocks (`xs.map { }.select { }.sum`) fuse into the same single loop, without delegates, so use whichever reads better.

Keep the counter unmodified inside the body. A loop that writes its own counter, or calls through an arbitrary stored lambda, loses these guarantees.

## Keep arrays local, and let the compiler own them

A local array that is only indexed, sized, iterated, lent to a parameter, moved to another local, or returned is [owned](https://alice-lang.org/documentation/advanced/ownership.md): its buffer is recycled instead of collected. Rolling rows in dynamic programming, per-frame scratch buffers, and temporary partitions then allocate once.

- Prefer a fresh `Array(T).new(n)` plus a move (`previous = row`) over copying.
- Pass buffers to helpers that only read and write them; storing one in a field ends ownership.
- Add `own` where you rely on it, so a later refactoring that leaks the array is a compile error (AL0207) instead of a silent slowdown.

Small arrays of literal size (up to 256 unmanaged elements, or a literal of up to 32 items) that never escape go on the stack. Arrays of fixed-width literal rows filled by one loop become one flat array.

## Shape work for parallelism

Large loops and recursive divide-and-conquer run across cores when the compiler proves them independent. Write them so the proof is easy:

- Each iteration writes only `out[i]`, or its row `img[y * w + x]` with `x` counting `0...w`.
- Read the previous state from a different array than the one you write (double buffering), not from the same array at `i - 1`.
- Keep reductions to one `total += value`.
- Split recursive work into two named calls: `left = solve(a)` / `right = solve(b)`, or `a, b = f(x), g(y)`.
- Keep the methods those loops call pure: no I/O, no writes to fields or parameters' arrays.

`par for` asks for the parallel loop at any trip count and reports AL0209 if the proof fails. See [fork/join and parallel loops](https://alice-lang.org/documentation/concurrency/fork-join.md).

## Help the JIT

.NET compiles methods twice: a quick first version, then an optimized one guided by profiling (tiered PGO). Alice works with that:

- Self-recursive functions over primitives and primitive arrays that return a primitive, the classic compute kernel, are compiled fully optimized from the first call, so their hottest frames never run unoptimized code.
- `@[Hot]` requests the same for any method; `@[Inline]` and `@[NoInline]` control inlining. Prefer measuring before adding them: object-heavy code usually gains more from PGO than from early optimization.
- Classes without Alice subclasses are sealed, so their calls are direct.

## Choose the collector for the workload

Generated programs use workstation concurrent GC: fast startup and low memory, right for CLIs, tools and games. Long-running services or parallel code that allocates heavily can opt in to one heap per core:

```toml
[runtime]
gc = "server"
```

When comparing with another .NET program, compare under the same GC: a C# project that sets `ServerGarbageCollection` is measuring a different collector.

## When to go native

`@[Native]` functions are compiled to C with clang, with the managed body as the fallback, for the rare kernel where the JIT's code is not enough. Use it after profiling, on numeric code that does not touch .NET objects.

## Checklist

| Goal                               | Write                                                                      |
| ---------------------------------- | -------------------------------------------------------------------------- |
| No overflow branches in hashes     | `&+ &- &*` or `unchecked ... end`                                          |
| One loop instead of several passes | literal-block iterator chains, or one `for`                                |
| No per-iteration allocation        | local arrays that stay local; `own` to enforce                             |
| Parallel loops                     | writes to `out[i]` / `img[y * w + x]`, reads from other arrays, pure calls |
| Parallel recursion                 | two named recursive calls, or `a, b = f(x), g(y)`                          |
| Guaranteed parallelism             | `par for`                                                                  |
| Faster kernels from the first call | primitive-only recursive functions, or `@[Hot]`                            |

## Continue reading

- Previous: [Test Alice programs](https://alice-lang.org/documentation/guides/testing.md)
- Next: [Understand performance](https://alice-lang.org/documentation/guides/performance.md)
