defk explores one question: can a typed subset of Elixir compile to native code that runs at C speed, with a cheap boundary, while keeping the BEAM's promises (preemption, isolation, cleanup)?
For loops over numbers and bytes the answer was yes. You
write a kernel with defk in an ordinary module. It reads like Elixir,
takes typed numbers and packed binaries, and compiles to a checked native
function (a NIF) when the module compiles.
The goal of defk is not to beat Elixir. It is to find the jobs where Elixir today pushes people towards a hand-written NIF, and to offer something better there.
Elixir stays the default. When an Elixir solution is as good or as fast, it is the right choice. So before claiming defk should be used anywhere, we optimise the Elixir solution as far as we can, and we keep the cases where Elixir wins as part of the record.
examples/ is a growing catalog of real use cases, each written in
Elixir and in defk, checked for agreement and measured end to end. The
measurement rules are in examples/README.md; the
scope page says when to use defk, when to use something else,
what is planned and what stays out by design.
Speedups over an optimised Elixir baseline, measured end to end on an otherwise idle M4 Pro on 2026-10-13 (a frozen snapshot; every number is one machine, so measure your own job). ✅ defk helps; 🟡 and ❌ use Elixir.
| The job | defk vs best Elixir | |
|---|---|---|
| ✅ | non-cryptographic hashes that must match other systems (xxHash64; Murmur3, SipHash and CRC32C beside it) | 209× |
| ✅ | FFT and convolution over audio-sized f64 signals | 73× |
| ✅ | BLAKE2b and BLAKE3 from scratch, level with the reference C NIF | 32× |
| ✅ | cosine top-k over 100k resident f32 embeddings (Nx with EXLA is faster still for a plain dot product) | 13× |
| ✅ | a bounded CDCL SAT solver on an authorisation job, 4× faster than picosat behind Crux | 13× |
| ✅ | 1080p image resize, blur, YUV420 conversion and dithering on raw frames, bit-exact | 7–14× |
| ✅ | rolling sensor statistics, HyperLogLog updates, PCM mixing with clip prevention, a byte histogram | 8–11× |
| ✅ | base64, hex and base58 codecs (4× the fast64 NIF); Snappy and LZ4 block decode on untrusted input | 5–7× |
| ✅ | sorting packed keys, nearest of 500k locations, Monte Carlo slot simulation with :rand bit for bit, Gorilla encoding |
2.4–6× |
| ✅ | a fuzzy-search web service, Myers bit-parallel on both sides: /health p99 stays at Elixir's 12–13 ms where a blocking NIF pushes it to 18–20 ms |
2.3–2.4× per query |
| 🟡 | a dictionary-wide Levenshtein batch over all schedulers: Myers bit-parallel in Elixir is close | 1.7× |
| 🟡 | small WebSocket frames, UTF-8 validation, substring search: the BIFs are native already | 1.0–1.1× |
| ❌ | anything :crypto does (a SHA-256 chain) and memory-hard password hashing (Argon2: the C runs its lanes on threads) |
0.25–0.55× |
| ❌ | one kernel call per element | 0.28× |
| ❌ | data that arrives as lists: packing costs more than the whole Enum.sum |
0.08× |
| ⛔ | maps and string building | not expressible (a string literal is a read-only [u8]) |
defmodule Stats do
use Defk
defk dot(a :: [f64], b :: [f64]) :: f64 do
for i <- 0..(length(a) - 1)//1, reduce: 0.0 do
acc -> acc + a[i] * b[i]
end
end
endiex> bin_a = for x <- [1.0, 2.0], into: <<>>, do: <<x::float-64-native>>
iex> bin_b = for x <- [3.0, 4.0], into: <<>>, do: <<x::float-64-native>>
iex> Stats.dot(bin_a, bin_b)
11.0A [f64] argument is a binary of packed native-endian floats. At mix compile, defk type-checks the body, emits C, builds a NIF with cc -O3,
and generates the Elixir stubs plus @specs. A construct outside the kernel
subset is a compile error in Elixir's diagnostic format, with why it's
rejected, a rewrite where one exists, and a stable code. Every kernel also
compiles to plain Elixir (Elixir mode), so tests can check the two agree.
Needs Elixir ≥ 1.20 (OTP 29 tested) and a C compiler (cc, or set CC) on
the machine that compiles your code. To use it, add it as a dependency
({:defk, "~> 0.1"} in a Mix project's deps, or
Mix.install([{:defk, "~> 0.1"}]) in a script) and
write kernels in any module with use Defk.
- Getting started: a first kernel, step by step.
- Testing kernels: check a kernel against Elixir.
- Measuring: compare with an optimised Elixir version honestly.
- Runtime contract: preemption, errors and cleanup.
- Deploying: releases and the C compiler.
- Stability: what stays the same from release to release.
- Cheatsheet and the full language reference.
- Compile errors: every code, why and what to write instead.
- Scope: what defk is for, what is planned and what stays out.