HervéRenault.fr

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.

Note : à refaire avec Python 3.
#!/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