Comparaison de langages de programmation

Date: 2025-04-21

Tags: python soft stats

On entend souvent parler de pseudo-guerres entre les langages de programmation.

Ça vaut le coup de les comparer à partir de critères arbitraires pour voir si leur popularité est justifiée pour chaque application.

Critères

Commençons par lister des critères :

Ces critères sont arbitraires et vont dépendre de ma familiarité avec les langages et bibliothèques, et de décisions d’utiliser une bibliothèque plutôt qu’une autre ou même aucune.

Conditions

Comme on va comparer des langages très différents, on va considérer des cas réalisables par ces langages, mais pour lesquels ils ne sont pas forcément optimaux.

On utilise les options par défaut des compilateurs et interpréteurs, à priori sans optimisations.

On considère que le code doit fonctionner sans aucune erreur ni avertissement, et doit s’exécuter correctement en produisant un résultat attendu. Le programme doit utiliser assez peu de mémoire pour s’exécuter sans ralentir les autres processus en tâche de fond.

On va aussi utiliser les mêmes paramètres pour chaque langage : même machine, mêmes dimensions de tableaux, même précision de variables et même nombre d’itérations de boucles.

On mesure la durée d’exécution avec le programme hyerfine et les options –warmup 1 –runs 10. On considère –run 5 lorsque la durée d’une exécution est supérieure à une minute.

Langages sélectionnés

C

Le langage C est réputé pour être un langage bas-niveau dont l’écriture est complexe et lente, avec une longue étape de compilation qui affiche souvent des erreurs ou avertissements en cas de code ambigü. Par contre, les temps d’exécution sont relativement rapides et le programmeur maîtrise l’utilisation de la mémoire.

Python

Python est réputé pour être le contraire du C : un langage haut-niveau dont l’écriture est rapide et plutôt simple. Aucune compilation n’est nécessaire puisque le langage est interprété, et l’interpréteur est réputé tolérant avec les erreurs. Par contre, la gestion de la mémoire n’est pas apparente du point-de-vue du programmeur, et les temps d’exécution sont réputés assez lents.

Python + bibliothèques

Le langage Python est rarement utilisé seul, et il existe souvent des bibliothèques qui permettent d’écrire un code plus simple et plus rapide, et d’optimiser certaines opérations. On utilise Numpy.

Bash

Bash est un langage de script que j’utilise régulièrement pour rendre des opérations semi-automatiques, mais rarement pour des programmes autonomes, bien qu’il fonctionne assez bien pour manipuler des nombres entiers et des objets divers. L’écriture est relativement rapide mais l’exécution est réputée extrêmement lente, sans aucun moyen de contrôler l’utilisation de la mémoire.

Go

J’ai longtemps entendu parler du langage Go comme langage orienté objet alternatif à Python, avec les performances du C, et la réputation d’avoir les avantages des deux langages : - Compilation plus rapide qu’en C - Exécution plus rapide qu’en Python - Fichiers exécutables compilés statiquement, indépendants des bibliothèques et de leurs versions - Bibliothèques liées au projet et indépendantes de l’environnement de développement

La durée d’écriture ne sera pas pertinente, puisque je ne connais pas la syntaxe du langage Go et que je vais devoir l’apprendre.

Rust

Rust est un langage dont j’ai longtemps entendu parler comme un remplaçant potentiel au C, qui permettrait au programmeur de gérer la mémoire de façon plus fine.

Ce langage a récemment reçu de l’attention comme étant intégré au noyau Linux, qui était écrit en C et en Assembleur.

Dans ce cas, je vais devoir apprendre à utiliser ce langage, et la durée d’écriture n’est pas pertinente.

Approximation de Pi par la méthode Monte-Carlo

L’algorithme est basé sur les statistiques : on considère un quart de cercle de rayon 1, inscrit dans un carré de côté 1.

Animation représentant le calcul de Pi par la méthode Monte Carlo

On génère N points avec deux coordonnées aléatoires distribuées normalement entre (0, 0) et (1, 1). Il reste à compter les points dont les coordonnées sont inscrites dans ce quart de cercle, divisé par le nombre total de points générés.

Le nombre de points N doit-être récupéré comme argument en ligne de commande, et le résultat doit approcher π4\frac{\pi}{4} et être affiché dans la console.

Code C

On écrit l’algorithme en l’adaptant à la syntaxe du C :

for(i=0; i<n; i++) {
    x = (double) rand() / RAND_MAX;
    y = (double) rand() / RAND_MAX;
    if(x*x+y*y < 1)
        count++;
}
printf("%f\n", (double) 4 * count / n);

Code Python

On a quasiment le même code à la syntaxe près :

for i in range(0, n):
    x = random.random()
    y = random.random()
    if(x*x+y*y < 1):
        count += 1
print(4*count/n)

Code Python + Numpy

Numpy permet d’exécuter des opérations sur des vecteurs et d’éviter l’utilisation de boucles. Le programme est trop simple pour gagner du temps sur l’écriture, et en perd même, puisqu’il faut se documenter pour des fonctions moins usuelles que celles de base. Mais on peut espérer gagner en durée d’exécution.

x = rng.random(n)
y = rng.random(n)
circle = x*x + y*y
count = np.where(circle < 1, 1, 0)
print(4*count.sum()/n)

Code Bash

Il y a quelques subtilités. Le langage n’est pas fait pour manipuler des nombres à virgule flottantes, alors on doit utiliser des nombres entiers. C’est pour cette raison que le diamètre du cercle est de 1’073’741’823 au lieu de 1.

$RANDOM retourne des nombres entiers entre 0 et 32’768 centrés sur 16’384. On y applique la formule de calcul du cercle 16’384²+16’384² = 1’073’741’824. On aurait pu utiliser la variable aléatoire $SRANDOM qui retourne des entiers de 32 bits, sauf qu’elle risque de déborder rapidement.

for i in $(seq 0 $n):
    do let x=$RANDOM
    let y=$RANDOM
    let circle=$x**2+$y**2
    if [ $circle -lt 1073741823 ]; then
        let count=$count+1
    fi
done

pi=$(awk "BEGIN{print 4 * $count / $n}")
echo $pi

Difficultés

L’exécution fonctionne lentement mais correctement pour de faibles nombres d’itération, mais la consommation mémoire devient rapidement exagérée au point de devoir terminer le programme manuellement parce qu’il utilise trop de ressources. La commande pmap retourne la quantité de mémoire réservée par un processus. Dans ce cas, on obtient 4GB après 30 secondes d’utilisation. C’est impossible à exécuter avec les mêmes paramètres et les mêmes conditions que les autres langages.

Code Go

Le code est très similaire à celui écrit en C, et des casts sont aussi nécessaires :

for i := 0; i <= n; i++ {
    x = rand.Float64()
    y = rand.Float64()
    if x*x+y*y < 1 {
        count++;
    }
}
fmt.Println(4*float64(count)/float64(n))

Code Rust

On re-écrit l’algorithme original en Rust :

for _i in 0..n {
    x = rng.random::<f64>();
    y = rng.random::<f64>();
    if x*x+y*y < 1.0 {
        count += 1;
    }
}
println!("{}", 4.0 * (count as f64) / (n as f64));

Optimisations

Les résultats n’étaient pas satisfaisants, l’exécution de la boucle était beaucoup trop lente, avec une durée dépassant deux minutes.

Commenter des blocs de code tour par tour a permis d’identifier la cause de la lenteur autour de l’exécution de la boucle.

La réecriture de la boucle n’a rien amélioré, et il aurait ne pas fallu changer le paramètre opt-level avant de rajouter l’option –release.

Utiliser simplement le paramètre –release utilise implicitement le paramètre opt-level = 3 et permet des durées d’exécutions très rapides.

Résultats

Langage C Python Numpy Bash Go Rust
Durée d’écriture de code [min] 10 5 10 15 30 45
Durée de compilation [ms] 0.05 0 0 0 0.05 7600
Durée d’exécution [s] 3.81 18.07 9.25 \infty 4.18 1.49
Nombre de lignes 22 19 17 19 27 30
Profondeur de boucles 1 1 0 1 1 1

Le nombre de lignes de code, la profondeur de boucles et la durée d’écriture du code dépendent beaucoup de l’utilisateur et ne devraient pas être utilisées comme seul moyen de comparaison.

Comparaison de durées d’exécution en C, Python, Go et Rust

La bibliothèque Numpy est écrite en C++ et permet de gagner un facteur 2 sur la vitesse d’exécution par rapport à Python.

Le langage Go était supposé plus rapide que Python, mais c’est assez comparable au C.

La surprise vient de Rust qui était censé être comparable au C, et est nettement plus rapide.

Le code est public CC BY-NC.

Références

Electronics Électronique puissance semiconducteur semiconductors power Hardware CPE INSA Xavier Bourgeois

Xavier