Tags : CSV, Java

Suite à la publication du billet MoteurCsv le JPA du CSV, quelqu'un m'a demandé si j'avais fait un bench. N'en ayant pas fait, je me suis lancé.
Les sources du bench sont disponibles sur github

Génération du fichier de test

Le "cahier des charges" fournit par "bbo" était le suivant :
  • ~10 000 000 de lignes.
  • ~200 caractères par ligne.
  • ~20 champs (4/5 gros champs autour de 20 caractères.)
C'est avec ces contraintes que j'ai créé la classe GenerationFchierCsv.
Cette classe génère donc un fichier de 10 000 000 de lignes contenant des lignes avec 20 champs :
  • Quatre champs de type String et de 30 caractères
  • Quatre champs de type Boolean (1 ou 0)
  • Quatre champs de type Integer et de 5 chiffres
  • Quatre champs de type Double et de 5 chiffres avant la virgule et 5 chiffres après la virgule
  • Quatre champs de type String et de 5 caractères

Voici le résultat de la génération du fichier :
Statistiques sur la taille des lignes :
  • Minimum : 215 caractères
  • Maximum : 227 caractères
  • Moyenne : 225,666 caractères
Taille du fichier : 2 266 666 035 octets soit 2,11 Go
Génération en 70 576ms
Le résultat est donc à peu près conforme au cahier des charges.

Constitution du bench

Le bench est réalisé par la classe Bench (je sais, je suis trop bon pour trouver des noms).
Pour le bench, je ne pouvais pas utiliser la méthode permettant de parser tout le fichier et qui renvoie une liste d'objet. Vu la taille du fichier, il est facile de comprendre que le mettre entièrement en mémoire ne serait pas une bonne idée.
Heureusement le moteur contient une autre méthode permettant de réaliser un traitement pour chaque ligne : MoteurCsv.parseFileAndInsert
Dans le cadre du bench j'ai donc utilisé cette méthode en ne réalisant aucun traitement :
public static void bench1() throws FileNotFoundException {
long startTime = System.currentTimeMillis();
moteur.parseFileAndInsert(new FileReader(fichier), ObjetCsv.class,
new InsertObject<objetcsv>() {
@Override
public void insertObject(ObjetCsv objet) {
// On ne fait rien dans le cadre du bench.
}
});
long elapsedTime = (System.currentTimeMillis() - startTime);
System.out.println("Lecture du fichier : " + elapsedTime + "ms");
}

J'ai également créé une méthode permettant de voir l'utilisation mémoire au fur et à mesure du test.
Le but de cette méthode est de regarder la mémoire occupée avant et après un GC entre chaque itération du bench. Cela permettra de vérifier entre autre qu'il n'y ait pas fuite mémoire.
public static void gestionMemoire() {
// Mémoire totale allouée
long totalMemory = Runtime.getRuntime().totalMemory();
// Mémoire utilisée
long currentMemory = totalMemory - Runtime.getRuntime().freeMemory();
System.out.println("Mémoire avant gc : " + (currentMemory / 1024) + "ko/" + (totalMemory / 1024) + "ko");
System.gc();
// Mémoire totale allouée
totalMemory = Runtime.getRuntime().totalMemory();
// Mémoire utilisée
currentMemory = totalMemory - Runtime.getRuntime().freeMemory();
System.out.println("Mémoire après gc : " + (currentMemory / 1024) + "ko/" + (totalMemory / 1024) + "ko");
}

Résultats

Les tests ont été menés sur un MacBookPro équipé d'un disque SSD et d'un Code i5.
ÉtapeTempsMémoire occupée avant GCMémoire occupée après GC
Avant de commencer/1 734ko338ko
Instanciation du moteur58 222µs14 049ko387ko
Lecture du fichier (itération 1)65 902ms14 049ko387ko
Lecture du fichier (itération 2)65 864ms13 796ko341ko
Lecture du fichier (itération 3)64 638ms14 059ko341ko
Lecture du fichier (itération 4)65 214ms13 700ko341ko
Lecture du fichier (itération 5)66 560ms14 027ko341ko

Ces résultats permettent de montrer plusieurs choses :
  • Temps pour instancier le moteur : quasi-null
  • Mémoire persistante pour le moteur : quelques Ko
  • Performances plutôt satisfaisantes avec un peu plus d'une minute pour lire un fichier de plus de 2Go

J'ai également fait du profiling avec YourKit afin de vérifier le comportement interne du moteur, cela a montré que la majorité du temps est passé dans la librairie open-csv, l'overhead du moteur est donc plutôt faible.

Reproduire le bench

N'hésitez pas à reproduire le bench et à me dire les résultats que vous obtenez :
  • Cloner le projet depuis github : github.com/ybonnel/BenchMoteurCsv
  • Importer le projet en tant que projet maven dans Eclipse
  • Lancer d'abord le main de la classe GenerationFchierCsv afin de générer le fichier de test
  • Lancer ensuite le main de classe Bench afin de lancer le bench en lui-même
Si vous connaissez d'autres parseurs CSV n'hésitez pas à ajouter des benchs dans le projet pour comparer les performances avec d'autres parseurs.