Rust est-il plus rapide que le C ?
(steveklabnik.com)- La comparaison des performances entre Rust et le C est une question complexe, dont la réponse dépend de la définition de l’hypothèse « toutes choses étant égales par ailleurs »
- Dans le cas de l’assembleur inline, les deux langages peuvent générer exactement le même code assembleur, donc il n’y a pas de différence de vitesse propre au langage
- Pour la disposition mémoire des structures, Rust peut obtenir une taille plus petite grâce au réordonnancement des champs, mais l’attribut
#[repr(C)]permet aussi d’avoir exactement la même disposition qu’en C - En pratique, les vérifications à l’exécution et les habitudes des développeurs peuvent entraîner des différences de structure du code et de performances
- En conclusion, il n’existe pas de différence de performances due aux limites intrinsèques du langage ; le résultat varie selon le projet et les développeurs
Le problème posé et l’ambiguïté des hypothèses
- Le point de départ est une question soulevée sur Reddit : « À conditions égales, Rust peut-il être plus rapide que le C ? »
- L’expression « toutes les conditions sont identiques » est elle-même très difficile à définir lorsqu’on compare des langages
- Une comparaison de performances dépend non seulement des différences entre langages, mais aussi de la forme du code, des choix de développement et des optimisations du compilateur
Comparaison de l’assembleur inline
- Rust prend en charge l’assembleur inline au niveau du langage, tandis que le C l’implémente via des extensions de compilateur
- Dans les deux langages, on peut écrire le même exemple utilisant l’instruction
rdtsc - L’assembleur généré par
rustc 1.87.0etclang 20.1.0est exactement identique
- Dans les deux langages, on peut écrire le même exemple utilisant l’instruction
- Ce cas ne montre pas une différence de performances entre langages, mais il prouve que Rust permet le même niveau de contrôle bas niveau que le C
Différences de layout des structures
- Les structures Rust peuvent optimiser l’usage mémoire via le réordonnancement des champs
- Dans l’exemple, la structure Rust occupe 16 octets, contre 24 octets pour la structure C équivalente
- En C, il faut modifier manuellement l’ordre des champs pour obtenir la même taille
- En Rust, l’attribut
#[repr(C)]permet d’imposer exactement la même disposition mémoire qu’en C
Facteurs sociaux et humains
- Grâce aux vérifications de sûreté de Rust, il existe des cas où les développeurs peuvent tenter des optimisations plus agressives
- Dans le projet Stylo de Mozilla, deux tentatives de parallélisation en C++ ont échoué, alors que l’implémentation en Rust a réussi
- Même sur un projet identique, les performances et la stabilité du code produit peuvent varier selon le langage et le niveau de maîtrise des développeurs
- Entre débutants et experts, et selon la familiarité avec le langage, le résultat d’une « même tâche » peut différer, ce qui rend toute comparaison simpliste difficile
Vérifications à la compilation et à l’exécution
- Une grande partie des vérifications de sûreté de Rust est effectuée à la compilation, mais certaines restent faites à l’exécution
- Par exemple, lors d’un accès
array[0], Rust effectue une vérification des bornes, alors que le C ne le fait pas - En Rust, l’utilisation de
get_unchecked()permet d’obtenir le même comportement qu’en C
- Par exemple, lors d’un accès
- Lorsque le compilateur peut prouver la sûreté, les deux langages peuvent éliminer ces vérifications via l’optimisation
- Ces différences influencent la manière d’écrire le code et peuvent, au final, provoquer des écarts de performances
Conclusion
- Même en supposant que le C soit « le langage le plus rapide », rien n’empêche Rust d’atteindre le même niveau de performances
- Plus que les limites du langage lui-même, ce sont les caractéristiques du projet, les compétences des développeurs et les contraintes de temps qui déterminent les écarts de performances
- Ainsi, la question « Rust est-il plus rapide que le C ? » relève davantage du contexte d’ingénierie que d’une simple comparaison entre langages
9 commentaires
Dans l’embarqué, on code en tenant compte jusqu’à la taille des lignes de cache du matériel. La question, c’est jusqu’où un programmeur peut pousser l’optimisation extrême au niveau du langage, ainsi que les performances de la bibliothèque standard et du compilateur. De toute façon, comme les deux prennent en charge le bas niveau, la légère différence d’overhead sera probablement négligeable. Donc je ne pense pas que ce soit un débat très pertinent… Si une optimisation extrême est nécessaire, une intervention humaine finit de toute façon par s’imposer. Les compilateurs ne sont pas aussi parfaits qu’on pourrait le croire.
Je pense que Rust sera plutôt un remplaçant de C++ que de C. Le C est pratiquement le seul langage (peut-être même le dernier) dans lequel on peut deviner le code que le compilateur va produire…
Zig n’est pas mauvais non plus... TT
À force d’écrire, c’est devenu un résumé façon IA, snif snif
Vous ne l’avez pas fait exprès, n’est-ce pas ? Hé hé.
Cela dépend des capacités du compilateur.
Il suffit d’assembler le même code pour le voir.
On dirait que les gars de
ffmpegpensent que Rust n’est pas plus rapide que C, haha : https://www.memorysafety.org/blog/rav1d-perf-bounty/Avis sur Hacker News
En résumé, la vitesse maximale est presque la même, mais dans le code réel l’écart peut être important
En particulier, le multithreading est un facteur majeur. En Rust, toutes les variables globales doivent être thread-safe, qu’on utilise des threads ou non, et le borrow checker limite l’accès mémoire soit au partage, soit à la modification
Du coup, écrire du code multithread en Rust est presque la norme. À l’inverse, en C, créer des threads reste contraignant à cause des problèmes de compatibilité entre plateformes et des risques au débogage
Construire avec des threads n’est pas difficile en C, mais c’est plus lourd que
std::thread::spawn(move || { ... });en RustPlus que la sûreté mémoire, c’est le modèle de concurrence du langage qui a le plus d’impact. Go permet aussi de paralléliser facilement avec
go f(), même sans être memory-safePersonnellement, j’ai vu plus souvent des heisenbugs en Go
#pragma omp forsuffit aussi pour paralléliser simplement en CGrâce aux traits de Rust, on peut construire des abstractions plus rapides et plus souples
En C, on peut imiter ça avec des macros ou des pointeurs de fonction, mais en Rust l’appelant peut choisir entre dispatch dynamique et dispatch statique
En embarqué, les pointeurs de fonction perturbent le cache et pénalisent les performances, alors que les traits Rust permettent l’inlining et sont donc bien plus efficaces
Que ce soit en Rust ou en C, on finit de toute façon par manipuler les choses au niveau de l’octet, et aujourd’hui les outils de binary patching se sont améliorés, donc c’est plus facile à exploiter
Box<dyn Trait>dans une signature de fonction, l’appelant se retrouve forcé d’utiliser le dispatch dynamiqueAvec
impl Trait, le choix reste du côté de l’appelantPersonnellement, je considère que Rust, C et C++ appartiennent pratiquement à la même famille de langages bas niveau, donc les écarts de performances sont minimes
Les règles strictes d’aliasing de Rust favorisent l’optimisation, et l’UB (comportement indéfini) en C/C++ existe aussi dans une logique de performance
Les génériques de Rust et de C++ sont aussi bien plus puissants que ce qu’offre C, par exemple un tri fondé sur des templates est plus facile à optimiser en inline que
qsort()Je pense que ce genre de débat sur la vitesse entre langages n’a, la plupart du temps, pas grand sens
Plus que le langage lui-même, c’est l’implémentation du compilateur qui détermine les performances
Rust, C et C++ sont tous des langages bas niveau, mais tout dépend de ce qu’on entend par « rapide »
Parle-t-on de la vitesse maximale d’un code optimisé par des experts, ou de la probabilité qu’un développeur moyen écrive du code rapide dans les limites du budget ?
Mais avec une optimisation manuelle poussée, les différences entre langages disparaissent presque
Cela dit, Rust garde un léger avantage dans le fait qu’il est plus facile d’y écrire du code rapide
Je pensais que les points forts de Rust étaient le multithreading et l’allocation sur la pile
Grâce au modèle de propriété, on peut mettre davantage de choses sur la pile qu’en C/C++, ce qui réduit le surcoût de
malloc/freeComme ce sujet déclenche souvent des débats émotionnels, je voulais parler davantage de différences de manière de penser que de chiffres précis
Quand on parle de la « vitesse » d’un langage, il faut regarder deux choses
Rust et C ont très peu de vérifications à l’exécution, donc ils sont plus rapides que Python ou JS
En revanche, Rust transmet mieux les informations d’aliasing, ce qui laisse davantage de marge à l’optimisation
En mode debug, il peut être aussi lent que Ruby, mais en mode release il atteint des performances comparables à C
Par rapport à C, C++ ou Rust ont des fonctionnalités de compilation plus riches, ce qui facilite l’écriture de code rapide
Par exemple, ce code est pratiquement impossible à faire en C
En C, il faut des outils externes comme re2c
L’assembleur ne fait pas partie de la norme C, donc il est difficile de le comparer directement à Rust, et Rust est en réalité plus proche du projet GCC par sa nature
Dire qu’un langage est « rapide » dépend au fond de l’implémentation et du contexte
Plus que la vitesse intrinsèque du langage, c’est la combinaison compilateur + matériel qui a le plus d’influence
En moyenne, je ne sais pas quelle langue est la plus rapide, mais j’ai l’impression que c’est le C++ qui présente la plus grande dispersion.