Une comparaison de langages de programmation
Attention, cette page est obsolète !
Note : Si cette page vous intéresse et si vous cherchez plus d'exemples, jetez un œil à mon aide-mémoire PHP-Perl-Python-JavaScript-Java.
Bien sûr, il existe de très bons tests de performances exhaustifs pour tous les langages (notamment The Computer Language Benchmarks Game), mais j'ai voulu me faire une idée sur les langages que je connais, et sur une tâche courante, similaire à ce que j'ai eu à faire plus d'une fois dans le cadre de mon travail.
Voici donc un programme simple qui lit un fichier texte passé en argument, en extrait les mots qui contiennent la lettre e mais ne commencent ni ne finissent par la lettre e, compte le nombre d'occurences de ces mots, et affiche un classement dans l'ordre décroissant. J'ai composé le fichier texte en concaténant plusieurs livres libres du Projet Gutenberg de manière à obtenir un fichier d'environ 130 Mo. J'ai écrit ce programme en Java, Perl, Python, mais aussi en PHP (qui peut faire plus que du web) et Ruby (j'ai eu une occasion de travailler quelques semaines avec Ruby dans Sketchup), en gardant à l'esprit que dans le monde réel, la vitesse d'exécution d'un logiciel n'est pas le seul critère. La vitesse de développement et de mise au point importe au moins autant, et chaque ligne de code supplémentaire peut introduire un bug supplémentaire. À chaque situation ses contraintes...
J'indique ci-dessous les temps d'exécution que j'obtiens sur ma machine personnelle avec Python 2.7.3, Perl 5.14.2, Java 1.7.0, Ruby 1.9.3, et PHP 5.4.21, ainsi qu'une appréciation sur la lisibilité et le support d'Unicode de chaque version.
PHP
1 minute 9 secondes.
Pour l'utf8, il faut utiliser mb_strtolower au lieu de strtolower et un modificateur "u" sur le preg_split.
Détail : La fonction array_multisort permet de trier également les mots, mais on utilisera plus simplement arsort.
Autre détail : PHP 5.3 (obsolète) mettait 1 minute 40, et le caractère œ était mal géré.
<?php $fh = fopen($_SERVER["argv"][1], 'r'); while (($ligne = fgets($fh)) != null) { $ligne = mb_strtolower($ligne, 'UTF-8'); $mots = preg_split('/[\P{Latin}\d_]+/u', $ligne); foreach ($mots as $mot) { if (preg_match('/^[^e].*e.*[^e]$/i', $mot)) { if (isset($compteMots[$mot])) { $compteMots[$mot]++; } else { $compteMots[$mot] = 1; } } } } array_multisort(array_values($compteMots), SORT_DESC, array_keys($compteMots), $compteMots); foreach ($compteMots as $cle => $valeur) { printf("% 8d %s\n", $valeur, $cle); } ?>
Python
1 minute 16 secondes.
Code court, bonne gestion de utf8.
Détails : on gagne 2 secondes si on appelle lower() sur chaque ligne plutôt que sur chaque mot. On peut utiliser une fonction lambda pour le tri mais le sorted avec itemgetter(1) gagne 6 secondes.
#!/usr/bin/env python from operator import itemgetter import codecs, re, sys pattern = re.compile("[\W\d_]+",re.UNICODE) compteMots = {} fichier = codecs.open(sys.argv[1], "r", "utf-8") for ligne in fichier: for mot in re.split(pattern, ligne.lower()): if re.search("^[^e].*e.*[^e]$", mot): compteMots[mot] = compteMots[mot] + 1 if compteMots.has_key(mot) else 1 for (mot, compte) in sorted(compteMots.items(), key=itemgetter(1), reverse=True): print "% 8d %s" % (compte, mot)
Perl
41 secondes.
Code très court "à la Perl" ("idiomatique"), bonne gestion de utf8. A l'inverse de Python, il vaut mieux appeler lc() sur chaque mot plutôt que sur chaque ligne, sinon on perdrait 7 secondes.
#!/usr/bin/perl use open qw(:utf8 :std); open($fh, '<', $ARGV[0]); while ($ligne = <$fh>) { foreach (split(/[\P{Latin}\d_]+/, $ligne)) { $compteMots{lc($_)}++ if /^[^e].*e.*[^e]$/i; } } foreach (sort { $compteMots{$b} <=> $compteMots{$a} } keys %compteMots) { printf("% 8d %s\n", $compteMots{$_}, $_); }
Java
18 secondes.
Verbeux, mais l'autocomplétion dans eclipse soulage le problème. Excellente gestion de utf8.
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class test_perfs_manip_texte { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader(args[0])); HashMap<String, Integer> compteMots = new HashMap<>(); Pattern pattern = Pattern.compile("([\\P{IsLatin}]+)"); Pattern pattern2 = Pattern.compile("^[^e].*e.*[^e]$"); Matcher matcher2; int nombre; String line = new String(); while ((line = br.readLine() ) != null) { line = line.toLowerCase(); String[] mots = pattern.split(line); for (String mot : mots) { matcher2 = pattern2.matcher(mot); if (matcher2.matches()) { if (compteMots.containsKey(mot)) { nombre = compteMots.get(mot) + 1; } else { nombre = 1; } compteMots.put(mot, nombre); } } } List<String> clesTriees = sortByValue(compteMots); Iterator<String> i = clesTriees.iterator(); while ( i.hasNext() ) { String cle = i.next(); System.out.println(String.format("% 8d %s", compteMots.get(cle), cle)); } } public static List<String> sortByValue(final Map<String, Integer> map) { List<String> keys = new ArrayList<String>(); keys.addAll(map.keySet()); Collections.sort(keys, new Comparator<String>() { public int compare(String cle1, String cle2) { Integer valeur1 = map.get(cle1); Integer valeur2 = map.get(cle2); return valeur2 - valeur1; } }); return keys; } }
Ruby
Je mets ce langage un peu à part car je ne suis pas un spécialiste. J'ai juste eu l'occasion de travailler un peu avec pendant quelques semaines.
2 minutes 10 secondes.
Code court. Bonne gestion de utf8 sous Ruby 1.9 en installant unicode_utils (gem install unicode_utils
) et en mettant un flag u sur la regexp du split comme avec PHP.
#!/usr/bin/ruby1.9.1 require "unicode_utils/downcase" compteMots = Hash.new fichier = File.open(ARGV[0], "r:UTF-8") fichier.each do |ligne| mots = ligne.split(/\P{Latin}+/u) mots.each do |mot| mot = UnicodeUtils.downcase(mot) if ( mot =~ /^[^e].*e.*[^e]$/ ) compteMots[mot] = compteMots[mot] ? compteMots[mot]+1 : 1 end end end compteMots.sort{ |a,b| b[1] <=> a[1] }.each do |key_value| printf("% 8d %s\n", key_value[1].to_s, key_value[0]) end
Autres
- J'ai également fait une version en C avec uthash qui tourne en 16 secondes contre 18 en Java, ce qui confirme que Java est devenu vraiment très rapide. Je ne publie pas cette version car elle est bâclée et je ne veux pas y passer plus de temps.
- J'ai aussi une version en shell Bash qui tient en une ligne :) mais elle est un peu hors-jeu car je splite sur une liste de caractères au lieu de spliter sur une liste de "non-caractères" (\P{IsLatin}) comme dans les autres versions :
cat "$1" | tr ' .,;:!?()[]{}/\\\-—"\`«»_\t^M' '\n' | tr "'" '\n' | tr 'A-Z' 'a-z' | grep -i '^[^e].*e.*[^e]$' | sort | uniq -c | sort -rn
Cela dit, elle donne un résultat très proche et tourne en 38 secondes.