Roughly five times CPython 3.14, with calls that allocate nothing at all, and not one structural decision in it taken on faith.
The benchmark runs against whichever Python sits on your own machine, calibrates both sides to the same target duration and keeps the fastest run. A figure from somebody else's hardware tells you nothing about yours.
Run atom benchmark --compare and argue with your own numbers.
| Benchmark | Atom | Python | Ratio |
|---|---|---|---|
| loop | 100.2M/s | 19.6M/s | 5.10× |
| arith | 50.1M/s | 9.1M/s | 5.48× |
| branch | 100.2M/s | 14.7M/s | 6.81× |
Every structural decision in Quark was measured against the alternatives, and the alternatives are still in the test suite. Expressions compile down to closures rather than running through an interface. Names resolve to indices while they compile, globals into numbered slots and function locals onto a reusable stack, so a call allocates nothing at all.
Calls used to build a map on every invocation, which turned out to be the overwhelming majority of everything the interpreter allocated. Removing it was worth more than any of the micro-optimisation that came before.
| Strategy | Per op |
|---|---|
| Compiled closures | 2.37ns |
| Bytecode machine | 12.01ns |
| Interfaces | 13.14ns |
go test -bench Dispatch ./src/expr/.The value type has to stay at four machine words. At five, Go stops returning it in registers and roughly two thirds of the interpreter's speed goes with it. A test fails the build if the struct ever grows, because the cost is invisible in the diff and obvious only in the benchmark.
Closures win decisively for expressions, so the same treatment looked obvious for statements. It came out about 25% slower and was backed out. The expression win came mostly from choosing the operator while compiling, not from closures in themselves.
The usual next step after a tree walker, and it lost by a factor of five. Go has no computed goto, so every opcode leaves through the same switch and the processor sees one unpredictable branch instead of many predictable ones.
Not a matter of taste. Go's collector finds pointers through each type's pointer map, so a pointer hidden in the unused bits of a float64 is invisible to it. The object gets freed while it is still being used.
Quark is not distributed separately and there is nothing to configure. It is what runs when you type atom, and it carries the web server, the reverse proxy, the repl and the formatter with it.