A markdown table of contents generator implemented in 31 languages for performance comparison, with both single-threaded and multi-threaded variants.
toc [-s] <directory>- Recursively finds
.mdfiles - Outputs a markdown TOC with links
-sextracts first paragraph as summary
mise install # Install toolchains
mise run build # Build compiled versions
./benchmark.sh # Run full benchmarkThe sample-docs/ directory is gitignored. To populate it for benchmarking:
# Kubernetes docs (~1,500 files)
git clone --depth 1 https://github.com/kubernetes/website.git /tmp/k8s
mkdir -p sample-docs/kubernetes
mv /tmp/k8s/content/en/docs/* sample-docs/kubernetes/
rm -rf /tmp/k8s
# VS Code docs (~300 files)
git clone --depth 1 --filter=blob:none --sparse https://github.com/microsoft/vscode-docs.git /tmp/vscode
cd /tmp/vscode && git sparse-checkout set docs && cd -
mkdir -p sample-docs/vscode
mv /tmp/vscode/docs/* sample-docs/vscode/
rm -rf /tmp/vscode
# React docs (~200 files)
git clone --depth 1 https://github.com/reactjs/react.dev.git /tmp/react
mkdir -p sample-docs/react
find /tmp/react/src/content -name "*.md" -exec cp {} sample-docs/react/ \;
rm -rf /tmp/react
# Go docs (~475 files)
git clone --depth 1 https://github.com/golang/website.git /tmp/golang
mkdir -p sample-docs/golang
cp -r /tmp/golang/_content/* sample-docs/golang/
rm -rf /tmp/golangHardware: MacBook Pro M4 Pro (24GB RAM), macOS 15.2
Tool: hyperfine with 3 warmup runs
Reproduce: ./benchmark.sh
| # | Language | Time | Type |
|---|---|---|---|
| 1 | C++ (threaded) | 36 ms | compiled |
| 2 | Nim (threaded) | 41 ms | compiled |
| 3 | Go (threaded) | 44 ms | compiled |
| 4 | C (threaded) | 46 ms | compiled |
| 5 | Rust (threaded) | 47 ms | compiled |
| 6 | Odin (threaded) | 50 ms | compiled |
| 7 | OCaml (threaded) | 50 ms | compiled |
| 8 | Zig (threaded) | 55 ms | compiled |
| 9 | V (threaded) | 58 ms | compiled |
| 10 | D (threaded) | 63 ms | compiled |
| 11 | Swift (threaded) | 69 ms | compiled |
| 12 | Crystal (threaded) | 70 ms | compiled |
| 13 | Dart (threaded) | 82 ms | compiled |
| 14 | Bun (threaded) | 91 ms | jit |
| 15 | Gleam (threaded) | 105 ms | compiled/vm |
| 16 | Haskell (threaded) | 106 ms | compiled |
| 17 | Node.js (threaded) | 113 ms | jit |
| 18 | Kotlin (threaded) | 123 ms | jit |
| 19 | F# (threaded) | 136 ms | compiled |
| 20 | Java (threaded) | 138 ms | jit |
| 21 | C# (threaded) | 139 ms | jit |
| 22 | Deno (threaded) | 177 ms | jit |
| 23 | Scala (threaded) | 210 ms | jit |
| 24 | Elixir (threaded) | 266 ms | compiled/vm |
| 25 | Erlang (threaded) | 533 ms | compiled/vm |
xychart-beta
title "Threaded Performance (ms) - Top 15"
x-axis ["C++", Nim, Go, C, Rust, Odin, OCaml, Zig, V, D, Swift, Crystal, Dart, Bun, Gleam]
y-axis "Time (ms)" 0 --> 120
bar [36, 41, 44, 46, 47, 50, 50, 55, 58, 63, 69, 70, 82, 91, 105]
| # | Language | Time | Type |
|---|---|---|---|
| 1 | Rust | 46 ms | compiled |
| 2 | Go | 50 ms | compiled |
| 3 | C++ | 51 ms | compiled |
| 4 | Zig | 53 ms | compiled |
| 5 | Nim | 53 ms | compiled |
| 6 | C | 54 ms | compiled |
| 7 | OCaml | 61 ms | compiled |
| 8 | Odin | 63 ms | compiled |
| 9 | V | 65 ms | compiled |
| 10 | Crystal | 66 ms | compiled |
| 11 | D | 68 ms | compiled |
| 12 | Swift | 74 ms | compiled |
| 13 | Perl | 93 ms | interpreted |
| 14 | Bun | 94 ms | jit |
| 15 | Haskell | 99 ms | compiled |
| 16 | Python | 105 ms | interpreted |
| 17 | Ruby | 108 ms | interpreted |
| 18 | PHP | 121 ms | interpreted |
| 19 | Gleam | 128 ms | compiled/vm |
| 20 | Node.js | 129 ms | jit |
| 21 | Kotlin | 133 ms | jit |
| 22 | Deno | 146 ms | jit |
| 23 | F# | 149 ms | compiled |
| 24 | Dart | 150 ms | compiled |
| 25 | C# | 154 ms | jit |
| 26 | Java | 155 ms | jit |
| 27 | Julia | 308 ms | jit |
| 28 | Scala | 426 ms | jit |
| 29 | Elixir | 439 ms | compiled/vm |
| 30 | Erlang | 576 ms | compiled/vm |
| — | Lua | 10,300 ms | interpreted |
xychart-beta
title "Single-Threaded Performance (ms) - Top 15"
x-axis [Rust, Go, "C++", Zig, Nim, C, OCaml, Odin, V, Crystal, D, Swift, Perl, Bun, Haskell]
y-axis "Time (ms)" 0 --> 110
bar [46, 50, 51, 53, 53, 54, 61, 63, 65, 66, 68, 74, 93, 94, 99]
| Language | Single | Threaded | Speedup |
|---|---|---|---|
| Scala | 426 ms | 210 ms | 2.03x |
| Dart | 150 ms | 82 ms | 1.83x |
| Elixir | 439 ms | 266 ms | 1.65x |
| C++ | 51 ms | 36 ms | 1.42x |
| Nim | 53 ms | 41 ms | 1.29x |
| Odin | 63 ms | 50 ms | 1.26x |
| OCaml | 61 ms | 50 ms | 1.22x |
| Go | 52 ms | 44 ms | 1.18x |
| Node.js | 129 ms | 113 ms | 1.14x |
| V | 65 ms | 58 ms | 1.12x |
| F# | 149 ms | 136 ms | 1.10x |
| D | 68 ms | 63 ms | 1.08x |
| Erlang | 576 ms | 533 ms | 1.08x |
| Swift | 74 ms | 69 ms | 1.07x |
| Crystal | 66 ms | 70 ms | 0.94x |
| Haskell | 99 ms | 106 ms | 0.93x |
| Deno | 146 ms | 177 ms | 0.82x |
| Perl | 93 ms | 115 ms | 0.81x |
xychart-beta
title "Threading Speedup (multiplier)"
x-axis [Scala, Dart, Elixir, "C++", Nim, Odin, OCaml, Go, Node, V, "F#", D, Erlang, Swift, Crystal, Haskell, Deno, Perl]
y-axis "Speedup" 0 --> 2.2
bar [2.03, 1.83, 1.65, 1.42, 1.29, 1.26, 1.22, 1.18, 1.14, 1.12, 1.10, 1.08, 1.08, 1.07, 0.94, 0.93, 0.82, 0.81]
| Language | Size |
|---|---|
| C | 33 KB |
| C++ | 39 KB |
| Swift | 77 KB |
| Nim | 99 KB |
| Zig | 105 KB |
| V | 192 KB |
| Odin | 202 KB |
| Rust | 327 KB |
| Crystal | 426 KB |
| D | 1.1 MB |
| C# | 1.2 MB |
| OCaml | 1.3 MB |
| Elixir | 1.3 MB |
| Go | 1.7 MB |
| F# | 3.2 MB |
| Kotlin | 5.0 MB |
| Dart | 5.2 MB |
| Scala | 6.8 MB |
| Haskell | 18 MB |
| Bun | 58 MB |
| Gleam | 58 MB |
| Deno | 69 MB |
xychart-beta
title "Binary Size (KB) - Compiled Languages"
x-axis [C, "C++", Swift, Nim, Zig, V, Odin, Rust, Crystal, D, "C#", OCaml, Elixir, Go, "F#"]
y-axis "Size (KB)" 0 --> 3500
bar [33, 39, 77, 99, 105, 192, 202, 327, 426, 1100, 1200, 1300, 1300, 1700, 3200]
- C++ threaded is fastest overall — 36ms with std::thread
- Top 6 within 55ms — Rust, Go, C++, Zig, Nim, C all sub-55ms single-threaded
- Perl is fastest interpreted language — 93ms, faster than Bun JIT!
- Nim validates "compiles to C" claim — 53ms single-threaded, matches C/Rust
- Scala has best threading speedup — 2.03x (426ms → 210ms), JVM parallelism shines
- Dart threading is excellent — 1.83x speedup (150ms → 82ms)
- Threading helps most compiled languages — 1.1-1.4x speedup typical
- Threading hurts some JIT/interpreted — Deno (0.82x), Perl (0.81x) slower threaded
- Elixir benefits from BEAM concurrency — 1.65x speedup despite VM overhead
- BEAM VM languages are slow for CLI — Erlang 576ms, Elixir 439ms (startup cost)
- Scala JIT startup is heavy — 426ms mostly compilation overhead
- Julia has 300ms+ startup — JIT compilation kills CLI performance
- Binary size varies 2,000x — C (33KB) vs Deno (69MB)
| Compiled | JIT/VM | Interpreted |
|---|---|---|
| C, C++ | Bun | Lua |
| Crystal | C# | Perl |
| D | Deno | PHP |
| Dart | Elixir | Python |
| F# | Erlang | Ruby |
| Go | Gleam | |
| Haskell | Java | |
| Nim | Julia | |
| OCaml | Kotlin | |
| Odin | Node.js | |
| Rust | Scala | |
| Swift | ||
| V | ||
| Zig |
Most compiled languages have both single-threaded and multi-threaded implementations.
MIT