DOTS//CORE local · not synced

m03 · Burst · reading · 8 min

Burst — a second compiler for a smaller language

Two compilers, one language

The C# you write for DOTS is compiled twice. The normal path — Mono or IL2CPP — turns it into general-purpose managed code: garbage-collected, able to touch any object, safe against a huge range of programs. That generality has a price the first module already named: pointer-chasing, allocation, indirection. The second path is Burst, and it exists to pay a different bargain entirely.

Burst takes the same C# — the same methods, the same loops — and compiles them through LLVM, the optimizing backend behind Clang and much of modern native tooling. The output isn’t managed code that a runtime interprets; it’s native machine code, vectorized and scheduled about as aggressively as a C++ compiler would manage. A Burst-compiled OnUpdate is, at the instruction level, closer to hand-tuned C than to anything you’d normally associate with C#.

The key thing to hold onto: you don’t rewrite anything to get this. The system you wrote in the last module, the tight loop over contiguous components — mark it [BurstCompile] and it runs as native SIMD code. Leave the mark off and the identical source runs as ordinary managed code, several times slower. Same characters in the file; the compiler you route them through is the entire difference.

Why Burst can’t compile all of C#

Here’s the constraint that shapes everything about writing Burst code, and it follows directly from what an optimizer needs. LLVM produces fast code by reasoning about your program — proving that two pointers don’t overlap, that a value doesn’t change between two reads, that a loop has no hidden side effects, so it can reorder, vectorize, and cache aggressively. That reasoning is only sound if the program stays inside a world the optimizer can fully model.

Managed C# is not that world. A managed object reference can point anywhere on the GC heap; the garbage collector can move objects out from under native code; a virtual call can dispatch to arbitrary code the optimizer has never seen; an exception can unwind through anything. Every one of those is a fact the optimizer cannot pin down, and each one blocks the reasoning that makes native code fast. So Burst doesn’t try. It compiles a subset of C#: code that is blittable (data laid out identically in managed and native memory, no hidden representation), holds no managed references (no class instances, no string, no managed arrays — only struct data and native containers), and avoids the constructs (GC, virtual dispatch, exceptions-as-control-flow) that would defeat the optimizer.

That subset isn’t a limitation Unity imposed for taste. It’s the exact set of programs LLVM can compile to the kind of code the memory wall demands. “Blittable, no managed references” is the price of the optimization, and once you see it that way, the restrictions stop feeling arbitrary: each one removes a thing the optimizer couldn’t have reasoned through.

The layout and the compiler are the same bet

Notice how cleanly this stacks on what you’ve already built. The archetype/chunk machinery arranges component data into dense, contiguous, same-typed arrays. Burst is the compiler that turns a loop over those arrays into vector instructions that chew through them several elements at a time. Neither half is worth much alone: SIMD code over scattered memory still stalls on the memory wall, and perfectly contiguous memory walked by scalar managed code leaves most of the machine idle. DOTS is the two halves together — lay the data out so the machine can stream it, then compile the loop so the machine actually does.

cache line · 64 B fetched 64 B useful · 100% of the bandwidth you paid for Burst's target: a full cache line of same-typed component data, consumed by one vector instruction instead of one scalar op per element

What this buys you

You now know what Burst is — a second, optimizing compiler for a deliberately smaller language — and why its restrictions are the shape of what LLVM can make fast rather than a set of rules to memorize. The next lesson opens up the single most important thing that compiler does with your loops: vectorization, turning one-element-at-a-time arithmetic into instructions that process four or eight lanes at once, and why the contiguous layout is what makes it legal.

m03.l01