Benchmarks
Clause compared with Erlang/OTP on one machine: the same Erlang programs, compiled by erlc and by clau -O2, time their kernels after a warm-up call. These numbers change with every optimization.
- Geometric mean of kernel times, Clause -O2 against the BEAM JIT (OTP)
- 1.49× faster on average
- Kernels where Clause is faster
- 31 / 31
- Geometric mean on 2026-10-12, the first recorded run
- 20.2×
Kernel times
Median of 3 runs in milliseconds. The bar shows Clause's time divided by OTP's on a logarithmic scale.
| Kernel | OTP JIT | Clause -O2 | Clause ÷ OTP | |
|---|---|---|---|---|
| Data generation | ||||
| bigint_factorialComputes the factorial of 3,000 with big integers, 330 times | 1,620.17 | 212.76 | 0.13× | |
| binary_appendAppends 10,000 32-bit integers to a growing binary, 23,000 times | 1,080.93 | 802.57 | 0.74× | |
| list_of_tuplesBuilds a list of 200,000 small tuples, 260 times | 1,467.60 | 1,128.44 | 0.77× | |
| list_to_tupleConverts 1,000-element lists to tuples, 170,000 times | 1,586.38 | 1,278.69 | 0.81× | |
| map_putInserts 2,000 integer keys into a map one by one, 4,900 times | 1,605.84 | 1,519.51 | 0.95× | |
| string_buildBuilds 20,000 strings from integers and atoms, 1,000 times | 1,044.38 | 720.73 | 0.69× | |
| Data processing | ||||
| binary_matchSums 20,000 32-bit integers matched out of a binary, 49,000 times | 1,272.63 | 1,071.47 | 0.84× | |
| comprehensionList comprehension filtering and doubling 20,000 integers, 15,000 times | 1,407.08 | 893.85 | 0.64× | |
| int_to_listConverts 20,000 integers to strings and back, 2,000 times | 908.70 | 412.79 | 0.45× | |
| map_foldlMaps, then folds a list of 20,000 integers, 4,500 times | 1,500.77 | 1,113.04 | 0.74× | |
| map_updateIncrements every value of a 500-key map, 24,000 times | 1,151.28 | 669.97 | 0.58× | |
| small_mapUpdates a small state map 80 million times | 1,486.44 | 1,061.83 | 0.71× | |
| sortSorts a list of 20,000 random integers, 600 times | 1,112.37 | 466.55 | 0.42× | |
| term_to_binaryEncodes and decodes a large nested term, 4,300 times | 1,433.29 | 1,071.95 | 0.75× | |
| File I/O | ||||
| large_readReads a 16 MB file in 64 KB chunks, 370 times | 1,444.15 | 1,007.93 | 0.70× | |
| large_writeWrites a 16 MB file in 64 KB chunks, 200 times | 1,019.80 | 989.09 | 0.97× | |
| read_fileReads a whole 16 MB file in one call, 330 times | 1,485.52 | 1,483.03 | 1.00× | |
| read_lineReads a 20,000-line text file line by line, 52 times | 1,522.07 | 1,467.28 | 0.96× | |
| small_filesWrites, reads back and deletes 500 small files, 11 times | 1,209.75 | 1,116.40 | 0.92× | |
| Garbage collection | ||||
| explicit_collectForces garbage collection with 10,000 live tuples, 5,100 times | 1,547.98 | 639.85 | 0.41× | |
| grow_shrinkGrows the heap with a 200,000-element list, then drops it, 300 times | 1,041.41 | 861.75 | 0.83× | |
| long_lived_churnKeeps a sliding window of 10,000 live elements among garbage, 370 times | 1,351.27 | 1,095.82 | 0.81× | |
| short_processesSpawns 5,000 short-lived processes that each make garbage, 69 times | 1,667.28 | 1,037.98 | 0.62× | |
| Libraries | ||||
| qlcRuns the qlc transform over 27 queries and 3 modules, 11 times | 1,873.72 | 1,714.66 | 0.92× | |
| Message passing | ||||
| fan_in100 processes each send 1,000 messages to one, 25 times | 1,402.68 | 391.78 | 0.28× | |
| large_messageSends a 100,000-element list to another process and back, 560 times | 1,063.94 | 789.04 | 0.74× | |
| ping_pongBounces one message between two processes 2 million times | 1,504.97 | 904.20 | 0.60× | |
| ringPasses a token around a 1,000-process ring 3,050 times | 1,438.21 | 1,099.66 | 0.76× | |
| Loopback sockets | ||||
| tcp_connectOpens 900 TCP connections over loopback, echoing 25 messages of 1 KB on each | 1,418.85 | 1,047.85 | 0.74× | |
| tcp_echoEchoes 30,000 messages of 1 KB over loopback TCP | 1,455.62 | 1,136.06 | 0.78× | |
| udp_round_tripBounces a 1 KB datagram 27,900 times over loopback UDP | 1,458.07 | 1,196.82 | 0.82× | |
Whole programs
Wall time in seconds, startup included, median of the runs.
| Program | OTP JIT | Clause -O2 | Clause ÷ OTP | |
|---|---|---|---|---|
| bench_datagenBuilds lists, tuples, maps, binaries and big integers in bulk | 8.54 | 5.72 | 0.67× | |
| bench_dataprocSorting, folds, map updates, binary matching and conversions | 10.45 | 6.76 | 0.65× | |
| bench_fileSmall and large file writes and reads, line reads | 6.95 | 6.24 | 0.90× | |
| bench_gcShort-lived processes, heap growth and shrinking, live data churn | 5.83 | 3.69 | 0.63× | |
| bench_libraryThe qlc parse transform at run time, as qlc:q/1 uses it | 2.30 | 1.89 | 0.82× | |
| bench_msgPing-pong, fan-in, a process ring and large message copies | 5.69 | 3.28 | 0.58× | |
| bench_socketTCP echo, TCP connects and UDP round trips over loopback | 5.99 | 4.57 | 0.76× |
Progress
Geometric mean of Clause's time divided by OTP's on each recorded run day. Runs before 2026-10-21 were not pinned to a CPU set, so part of the change on that day comes from the pinning.
| Date | Commit | Clause ÷ OTP |
|---|---|---|
| 2026-10-12 | a7bc0c9b | 20.2× |
| 2026-10-13 | 8a244d9c | 16.9× |
| 2026-10-14 | 55f42a9d | 16.9× |
| 2026-10-15 | 6ed3851c | 2.73× |
| 2026-10-16 | 97295c84 | 1.95× |
| 2026-10-17 | a8c3f8f6 | 1.38× |
| 2026-10-18 | 336caea7 | 1.41× |
| 2026-10-20 | 7d7a2f2e | 1.00× |
| 2026-10-21 | 71ec52f8 | 0.78× |
| 2026-10-23 | dea68508 | 0.69× |
| 2026-10-24 | af105043 | 0.65× |
| 2026-10-26 | d6eafa72 | 0.71× |
| 2026-10-30 | f4e5f3e0 | 0.67× |
Executable sizes
Size of the benchmark programs as Clause builds them at each optimization level, in megabytes.
| Program | -O0 | -O2 | -O2 --lto |
|---|---|---|---|
| bench_datagen | 9.90 MB | 9.82 MB | 9.82 MB |
| bench_dataproc | 10.42 MB | 10.01 MB | 10.01 MB |
| bench_file | 11.34 MB | 10.59 MB | 10.59 MB |
| bench_gc | 9.89 MB | 9.81 MB | 9.81 MB |
| bench_library | 10.24 MB | 9.94 MB | 9.95 MB |
| bench_msg | 9.91 MB | 9.82 MB | 9.82 MB |
| bench_socket | 11.58 MB | 10.64 MB | 10.65 MB |
-O2 sizes of the last 6 benchmark runs, oldest first, one Clause commit each. The table below adds the total of all bench_* programs and the size of the clau compiler.
- 2026-10-207d7a2f2e
- 2026-10-2171ec52f8
- 2026-10-23dea68508
- 2026-10-24af105043
- 2026-10-26d6eafa72
- 2026-10-30f4e5f3e0
| Date | Commit | All bench_* programs | clau |
|---|---|---|---|
| 2026-10-20 | 7d7a2f2e | 38.59 MB | 59.50 MB |
| 2026-10-21 | 71ec52f8 | 38.78 MB | 59.54 MB |
| 2026-10-23 | dea68508 | 38.79 MB | 59.54 MB |
| 2026-10-24 | af105043 | 38.81 MB | 59.65 MB |
| 2026-10-26 | d6eafa72 | 62.21 MB | 59.98 MB |
| 2026-10-30 | f4e5f3e0 | 70.63 MB | 60.75 MB |
Setup
- Machine
- Intel64 Family 6 Model 183 Stepping 1, GenuineIntel, 32 logical CPUs, Windows 11 (10.0.26200)
- CPU set
- Performance cores 0-15, 16 of 32 logical CPUs
- Erlang/OTP
- 29, ERTS 17.1
- Clause commit
- f4e5f3e0
- Run
- 2026-10-30
OTP and Clause run on the same CPUs: every benchmark program is pinned to the performance cores of the processor, so on a hybrid CPU no kernel lands on a slower efficiency core. Both keep one scheduler online per CPU of that set, unbound within it.
To run the benchmarks yourself, use make benchmark or make-benchmark.bat in the Clause repository; the benchmark notes describe the programs and options. Benchmark notes
Clause