Huit langages, une machine

Quatre petits programmes, écrits une fois par langage et vérifiés comme imprimant les mêmes octets, chronométrés sur la même machine au repos — avec les contrôles qui disent lesquelles des différences sont réelles.

Les quatre programmes

Ils ont été choisis pour quatre coûts différents. Le résultat le plus utile de cette page est qu'ils ne s'accordent sur aucun classement.

ProgrammeTailleCe qu'il sollicite
fib(35)9,2 M d'appels récursifsCoût d'appel et arithmétique entière. Rien n'est alloué, donc la stratégie mémoire d'un langage ne peut ni l'aider ni le pénaliser.
loops100 M d'itérationsUne boucle while, un modulo, une addition. Le corps se compile en la même poignée d'instructions dans tout langage qui compile.
objects10 M d'allocationsUn record à deux champs construit et détruit à chaque itération. C'est là que comptage de références, ramasse-miettes et allocation sur la pile donnent des réponses différentes.
lists1 M d'éléments à travers map / filter / foldChaque langage utilise son propre type de liste et ses propres fonctions d'ordre supérieur. C'est le moins comparable des quatre, et la colonne où cela compte le plus.

Linux · aarch64 — invité aarch64 6 cœurs sous QEMU, 7 Go

Ubuntu, Linux 7.0.02026-09-04keal 1.2.018 runs

Temps de calcul

Chaque chiffre est le minimum des exécutions moins le temps de démarrage propre à ce runtime, pour que l'amorçage d'une JVM ne soit pas imputé à son arithmétique. Le minimum plutôt que la médiane : une machine perturbée produit une longue queue à droite et aucune à gauche.

Langagefib(35)loopsobjectslistsdémarrage
C9.143.110.13.90.2
C++9.343.810.25.80.3
Rust16.140.59.33.50.4
Keal20.444.210.013.40.3
Go22.746.211.130.90.8
Java15.343.914.297.314.9
Kotlin13.142.613.778.723.2
Python449.04 6841 46594.86.2

fib(35) — 9,2 M d'appels récursifs

C
1.0×
C++
1.0×
Rust
1.8×
Keal
2.2×
Go
2.5×
Java
1.7×
Kotlin
1.4×
Python
49×

loops — 100 M d'itérations

C
1.0×
C++
1.0×
Rust
0.9×
Keal
1.0×
Go
1.1×
Java
1.0×
Kotlin
1.0×
Python
109×

objects — 10 M d'allocations

C
1.0×
C++
1.0×
Rust
0.9×
Keal
1.0×
Go
1.1×
Java
1.4×
Kotlin
1.4×
Python
145×

lists — 1 M d'éléments à travers map / filter / fold

C
1.0×
C++
1.5×
Rust
0.9×
Keal
3.4×
Go
7.9×
Java
25×
Kotlin
20×
Python
24×

La solidité de chaque chiffre

De combien la médiane dépasse le minimum, en pourcentage. Sous 10% environ, un chiffre est acquis ; bien au-dessus, deux langages séparés de quelques pour cent ne sont pas départagés par ces données, et la page ne prétend pas le contraire.

Langagefib(35)loopsobjectslists
C9%7%8%19%
C++6%7%7%13%
Rust7%8%5%17%
Keal7%3%7%4%
Go3%5%5%89%
Java6%9%6%11%
Kotlin11%10%12%8%
Python8%3%5%7%

Chaînes d'outils

LangageVersionCompilation
Cgcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0-O2 -std=c11
C++g++ (Ubuntu 15.2.0-16ubuntu1) 15.2.0-O2 -std=c++17
Rustrustc 1.98.0 (88d9e12ae 2026-08-18)-C opt-level=2
Kealkeal 1.2.0keal build
Gogo version go1.25.1 linux/arm64go build
Javaopenjdk version "25.0.4" 2026-07-21javac, default JVM
Kotlinkotlinc-jvm 2.4.10 (JRE 25.0.4+7-1-26.04-Ubuntu); jars run on openjdk version "25.0.4" 2026-07-21-include-runtime, jars run on the PATH java
PythonCPython 3.14.4stock build

macOS · Apple M4 — Apple M4 10 cœurs (4 performance + 6 efficacité), arm64, matériel nu, 24 Go — la seule machine ici qui ne soit pas un invité

macOS 26.5.1, Darwin 25.5.02026-09-04keal 1.2.018 runs

Le JDK et l'interpréteur ci-dessus ont été mis en place pour cette mesure, ils ne sont pas ceux de la machine. Les deux installations sont keg-only : le java par défaut de ce Mac est en 23 et son python3 par défaut est le 3.9.6 d'Apple. Reproduire avec les valeurs par défaut de la machine donne d'autres chiffres — à lui seul, CPython 3.9 exécute fib 2,2 fois plus lentement que 3.14 ici, soit davantage que tout écart entre les machines sur cette ligne.

Temps de calcul

Chaque chiffre est le minimum des exécutions moins le temps de démarrage propre à ce runtime, pour que l'amorçage d'une JVM ne soit pas imputé à son arithmétique. Le minimum plutôt que la médiane : une machine perturbée produit une longue queue à droite et aucune à gauche.

Langagefib(35)loopsobjectslistsdémarrage
C16.432.56.02.51.8
C++16.432.86.02.92.0
Rust16.233.49.12.91.9
Keal20.945.49.510.41.7
Go20.446.09.77.32.2
Java14.242.712.548.718.9
Kotlin17.041.813.041.526.3
Python605.35 8601 688106.615.6

fib(35) — 9,2 M d'appels récursifs

C
1.0×
C++
1.0×
Rust
1.0×
Keal
1.3×
Go
1.2×
Java
0.9×
Kotlin
1.0×
Python
37×

loops — 100 M d'itérations

C
1.0×
C++
1.0×
Rust
1.0×
Keal
1.4×
Go
1.4×
Java
1.3×
Kotlin
1.3×
Python
180×

objects — 10 M d'allocations

C
1.0×
C++
1.0×
Rust
1.5×
Keal
1.6×
Go
1.6×
Java
2.1×
Kotlin
2.2×
Python
281×

lists — 1 M d'éléments à travers map / filter / fold

C
1.0×
C++
1.2×
Rust
1.2×
Keal
4.2×
Go
2.9×
Java
19×
Kotlin
17×
Python
43×

La solidité de chaque chiffre

De combien la médiane dépasse le minimum, en pourcentage. Sous 10% environ, un chiffre est acquis ; bien au-dessus, deux langages séparés de quelques pour cent ne sont pas départagés par ces données, et la page ne prétend pas le contraire.

Langagefib(35)loopsobjectslists
C8%8%13%12%
C++7%11%5%7%
Rust7%11%6%16%
Keal9%2%5%10%
Go8%5%12%27%
Java21%8%8%10%
Kotlin10%14%7%12%
Python8%8%11%21%

Chaînes d'outils

LangageVersionCompilation
CApple clang version 21.0.0 (clang-2100.1.1.101)-O2 -std=c11
C++Apple clang version 21.0.0 (clang-2100.1.1.101)-O2 -std=c++17
Rustrustc 1.98.0 (88d9e12ae 2026-08-18)-C opt-level=2
Kealkeal 1.2.0keal build
Gogo version go1.27.0 darwin/arm64go build
Javaopenjdk version "25.0.4.1" 2026-08-18javac, default JVM
Kotlinkotlinc-jvm 2.4.10 (JRE 25.0.4.1); jars run on openjdk version "25.0.4.1" 2026-08-18-include-runtime, jars run on the PATH java
PythonCPython 3.14.7stock build

Windows · x86_64 — invité AMD EPYC 9354 4 cœurs / 8 threads sur cloud PC Shadow, 16 Go ; une tranche d'un processeur 32 cœurs, dont le voisinage nous échappe

Windows 11, build 226212026-09-04keal 1.2.018 runs

Toutes les chaînes d'outils ci-dessus ont été installées pour cette mesure : la machine n'en avait aucune, pas même un Python fonctionnel. Rust est sur l'ABI GNU et non MSVC, ce que ses options indiquent — le C est ici MinGW, et un Rust MSVC aurait introduit un second runtime C dans la même colonne de rapports. keal build emprunte ce même gcc.

Temps de calcul

Chaque chiffre est le minimum des exécutions moins le temps de démarrage propre à ce runtime, pour que l'amorçage d'une JVM ne soit pas imputé à son arithmétique. Le minimum plutôt que la médiane : une machine perturbée produit une longue queue à droite et aucune à gauche.

Langagefib(35)loopsobjectslistsdémarrage
C11.192.019.37.74.9
C++11.491.719.610.85.0
Rust21.666.720.36.65.9
Keal45.3108.218.417.66.2
Go41.483.519.623.66.9
Java26.5108.832.4101.746.9
Kotlin24.7108.033.185.663.4
Python1 1309 2782 896235.421.4

fib(35) — 9,2 M d'appels récursifs

C
1.0×
C++
1.0×
Rust
1.9×
Keal
4.1×
Go
3.7×
Java
2.4×
Kotlin
2.2×
Python
102×

loops — 100 M d'itérations

C
1.0×
C++
1.0×
Rust
0.7×
Keal
1.2×
Go
0.9×
Java
1.2×
Kotlin
1.2×
Python
101×

objects — 10 M d'allocations

C
1.0×
C++
1.0×
Rust
1.1×
Keal
1.0×
Go
1.0×
Java
1.7×
Kotlin
1.7×
Python
150×

lists — 1 M d'éléments à travers map / filter / fold

C
1.0×
C++
1.4×
Rust
0.9×
Keal
2.3×
Go
3.1×
Java
13×
Kotlin
11×
Python
31×

La solidité de chaque chiffre

De combien la médiane dépasse le minimum, en pourcentage. Sous 10% environ, un chiffre est acquis ; bien au-dessus, deux langages séparés de quelques pour cent ne sont pas départagés par ces données, et la page ne prétend pas le contraire.

Langagefib(35)loopsobjectslists
C6%1%3%9%
C++5%1%4%5%
Rust1%2%4%7%
Keal1%1%3%4%
Go2%2%4%19%
Java5%3%4%5%
Kotlin5%1%2%3%
Python1%3%4%2%

Chaînes d'outils

LangageVersionCompilation
Cgcc.EXE (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0-O2 -std=c11
C++g++.EXE (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0-O2 -std=c++17
Rustrustc 1.98.1 (48a229cea 2026-09-01)-C opt-level=2, x86_64-pc-windows-gnu
Kealkeal 1.2.0keal build (gcc, MinGW-w64 UCRT)
Gogo version go1.27.1 windows/amd64go build
Javaopenjdk version "25.0.4.1" 2026-08-18 LTSjavac, default JVM
Kotlinkotlinc-jvm 2.4.10 (JRE 25.0.4.1+1-LTS)-include-runtime, default JVM
PythonCPython 3.12.10stock build

Les mêmes rapports, machine par machine

Là où deux machines ne s'accordent pas sur un rapport, le désaccord est le résultat — le chiffre était une propriété d'une machine et non du langage. Lisez le dénominateur d'abord : plusieurs lignes qui bougent ensemble et dans le même sens signent un changement de la référence C, et non cinq langages changeant à la fois.

fib(35)

LangageLinux · aarch64macOS · Apple M4Windows · x86_64
C1.0×1.0×1.0×
C++1.0×1.0×1.0×
Rust1.8×1.0×1.9×
Keal2.2×1.3×4.1×
Go2.5×1.2×3.7×
Java1.7×0.9×2.4×
Kotlin1.4×1.0×2.2×
Python49×37×102×

loops

LangageLinux · aarch64macOS · Apple M4Windows · x86_64
C1.0×1.0×1.0×
C++1.0×1.0×1.0×
Rust0.9×1.0×0.7×
Keal1.0×1.4×1.2×
Go1.1×1.4×0.9×
Java1.0×1.3×1.2×
Kotlin1.0×1.3×1.2×
Python109×180×101×

objects

LangageLinux · aarch64macOS · Apple M4Windows · x86_64
C1.0×1.0×1.0×
C++1.0×1.0×1.0×
Rust0.9×1.5×1.1×
Keal1.0×1.6×1.0×
Go1.1×1.6×1.0×
Java1.4×2.1×1.7×
Kotlin1.4×2.2×1.7×
Python145×281×150×

lists

LangageLinux · aarch64macOS · Apple M4Windows · x86_64
C1.0×1.0×1.0×
C++1.5×1.2×1.4×
Rust0.9×1.2×0.9×
Keal3.4×4.2×2.3×
Go7.9×2.9×3.1×
Java25×19×13×
Kotlin20×17×11×
Python24×43×31×

Ce qui a été vérifié avant de croire quoi que ce soit

Un banc d'essai est un instrument, et un instrument que personne n'a étalonné rapporte ses propres défauts comme des résultats. Chacun de ces contrôles pouvait revenir négatif.

32 / 32 concordent

Les programmes calculent la même chose

Chaque implémentation est exécutée et sa dernière ligne comparée à la référence Keal avant tout chronométrage. Un programme plus rapide qui imprime un autre nombre n'est pas un programme plus rapide.

écarté par mise à l'échelle

Aucun compilateur n'a calculé la réponse à la compilation

Dix fois le travail doit coûter dix fois le temps. Vérifié en reconstruisant à ×10 et en rechronométrant plutôt qu'en lisant l'assembleur — bien que la branche arrière de la boucle y ait aussi été confirmée. Dix fois le travail est revenu à 9,0–9,6 fois le temps.

deux plans comparés

Si l'ordre d'exécution déplace les chiffres

L'ensemble est mesuré deux fois : une fois groupé, chaque réplique d'une configuration d'affilée, une fois entrelacé dans un ordre tiré au sort. L'écart entre les deux plans est ensuite confronté à la dispersion propre à cette configuration à l'intérieur d'un seul plan. 1 sur 96 l'ont franchi — et avec un seuil aussi grossier appliqué 32 fois par machine, un franchissement marginal ou deux est ce que le hasard seul produit, si bien que le compte est rapporté plutôt qu'interprété.

aucune dérive

Rien ne s'est échauffé ni essoufflé en cours de route

Corrélation de rang entre le moment d'une exécution dans la séquence entrelacée et sa durée. Une machine qui chauffe, ou un cache qui se remplit, apparaîtrait comme un signe constant d'une configuration à l'autre. La médiane vaut 0,19, bien en deçà du seuil que neuf répliques rendent significatif.

un tour jeté

Le cache de pages était plein avant le départ du chronomètre

La première exécution d'un binaire le lit depuis le disque. Chaque configuration est exécutée une fois et jetée avant la mesure, pour qu'aucun langage ne paie le fait d'être passé en premier.

nommées, pas lissées

Les configurations trop instables sont désignées

Le tableau de dispersion n'est pas décoratif. Là où la médiane d'une configuration dépasse largement son minimum, sa position face à un voisin proche n'est pas tranchée par ces données, et la page le dit au lieu de désigner un gagnant.

Ce que cela n'établit pas

lists n'est pas comparable à l'identique. Le List<Int> de Keal, le vector de C++ et le Vec de Rust contiennent des entiers 64 bits non boxés ; Java et Kotlin boxent chaque élément dans un Long. Cette différence explique l'essentiel de ce que la JVM paie dans cette colonne, et c'est une propriété des bibliothèques plutôt que des compilateurs.

Chaque mesure est un processus entier, exécuté une fois. Pas de boucle de chauffe dans le programme, pas de mesure en régime établi. C'est ce que coûte l'exécution du programme, ce qui avantage les compilateurs anticipés et sous-estime ce qu'un JIT fait dans un serveur de longue durée.

Quatre programmes sont quatre programmes. Ils ont été choisis pour quatre coûts différents, mais aucun ensemble aussi petit ne prédit une charge réelle, et aucun ne touche aux entrées-sorties, aux chaînes ni à la concurrence.

Le rapport à C emporte le compilateur C avec lui. Un rapport divise par ce qu'a produit le compilateur C local : il absorbe la vitesse du matériel, pas les décisions d'optimisation de la référence elle-même. fib est l'endroit où cela se voit. Sur la machine Linux, gcc en -O2 déroule la récursion sur plusieurs niveaux — 244 instructions dans le corps de la fonction contre 30 sans inlining — et la même source, le même compilateur et le même drapeau s'exécutent alors en 11,8 ms contre 20,3 ms. Ce facteur 1,7 est de l'ordre de l'écart entre les références C des machines sur ce programme. objects est la seconde colonne dans ce cas, et elle pointe en sens inverse : face à la machine sous clang, six lignes sur sept montent ensemble d'environ 1,6× parce que c'est là que la référence C est la rapide. Entre les deux machines sous gcc, cette même colonne reste dans le peloton — c'est ce qui dit que l'effet appartient au compilateur et non au matériel. Ces deux rapports se comparent donc à l'intérieur d'une machine et non d'un compilateur C à l'autre, et toute ligne qui bouge d'une machine à l'autre doit être lue contre son dénominateur avant d'être lue comme un fait sur le langage.

La soustraction du démarrage pèse le plus là où il y en a le plus. Chaque chiffre est diminué du temps de hello-world de sa propre machine, ce qui garde l'amorçage d'une JVM hors de son arithmétique. Mais cet amorçage se compte en dizaines de millisecondes là où celui d'un binaire natif tient sous deux : les lignes JVM portent donc de loin la plus grosse correction — et elle bouge. Sur la machine macOS, aligner le JDK a déplacé le démarrage de Java de 8 ms, soit 8 ms sur ses quatre chiffres et un dixième de son lists. Comparer une ligne JVM d'une machine à l'autre, c'est comparer deux corrections de démarrage autant que deux runtimes.

Les machines ne partagent pas leur chaîne d'outils. Un rapport à C absorbe la vitesse du matériel. Il n'absorbe ni un gcc différent, ni un Go différent, ni une JVM différente, et chaque machine rapporte ce qu'elle avait. Là où le runtime est lui-même l'objet de la mesure — les lignes Java et Kotlin — les machines s'alignent sur une version majeure de JDK, faute de quoi un écart entre deux machines confondrait le système d'exploitation et la machine virtuelle. Partout ailleurs la version est déclarée plutôt qu'imposée, et le tableau des chaînes d'outils sous chaque machine est cette divulgation.

Un rapport est une propriété d'une machine tant qu'une seconde ne l'a pas confirmé. La première machine ici n'a pas reproduit un rapport que ce projet avait publié, d'un facteur deux, pour exactement cette raison. Tant que le tableau ci-dessous n'a qu'une colonne, lisez chaque chiffre comme mesuré et non comme vrai.

Le refaire soi-même

Les programmes sont dans bench/ports/ et le harnais qui les chronomètre est bench/ports/run.py. Il construit ce pour quoi il trouve une chaîne d'outils, nomme ce qu'il saute, et imprime une entrée prête à être ajoutée à la liste des machines. Chaque langage utilise des entiers signés 64 bits et, quand il a le choix, le niveau d'optimisation que keal build passe lui-même à cc.