I. Contexte
Nous vivons dans un monde où la programmation objet est considérée comme le Graal et la STL (Standard Template Library) comme la seule API à considérer. Et la majorité des développeurs appliquent les informations qu'on leur a martelées durant leur apprentissage. Et, trop souvent, la STL est survendue comme LA panacée ! Tout outil méconnu ou mal maîtrisé peut induire de mauvais choix.
Dans cet article, nous allons comparer les API de fichiers : en l’occurrence les « stream » versus l’API C de gestion de fichiers.
II. Un petit exemple
Essayons d’écrire un programme qui copie un fichier texte dans un autre fichier et effectuons cette tâche via les 2 méthodes suivantes : l’API C et via les streams de la STL… On va ouvrir le fichier en lecture, lire les lignes une à une et les écrire dans le fichier en écriture. On va utiliser la fonction « clock() » pour mesurer les performances des 2 méthodes.
De l’API C, nous allons utiliser :
fopen : ouverture du fichier
fclose : fermeture du fichier
feof : a-t-on atteint la fin du fichier
fgets : lire une ligne
fputs : écriture d’une ligne
De l’API de la STL, nous allons utiliser :
std::ifstream : stream sur fichier en lecture
std::ofstream : stream de fichier en écriture
std::getline : lire une ligne dans un stream
Après exécution en release 64 bits (compilé avec gcc), j'ai obtenu sur mon PC :
C : 579
STL : 4385
L'API C est 7,5 fois plus rapide que l'API STL. La différence de performance est très importante. Aussi, en utilisant le compilateur de Visual Studio 2017, on a une différence de performance équivalente. En effet, l'API est 8.6 fois plus rapide que la STL.
III. Mais pourquoi tant de haine ?
On m'avait promis que la programmation orientée objet était le nec plus ultra. Mais aussi que le C++ était l'aboutissement de l'évolution de l'informatique ! M'aurait-on menti ?
Il y a une explication ! La STL a été créée sur des concepts : la généricité, l'abstraction... Trop de généricité, dans le cas présent, nuit à l'efficacité via une complexité du code.
1. std::getline
Une des causes de contre-performance est l'implémentation de la fonction – std::getline, qui est la suivante dans l'implémentation Microsoft :
Une implémentation que je qualifierais d’assez complexe avec un gestionnaire d’exceptions, une belle boucle, des ‘if’ et des appels de fonctions. Il y a également une chaîne de caractères (std::string) dans laquelle on ajoute les caractères un à un : réallocations de mémoire cachée… Fgets a une implémentation plus simple qui travaille sur un buffer pré-alloué : moins d’allocations.
2. Appel de fonction
Chaque ‘<<’ est un appel de fonction qui finit par aboutir à :
Je pense que nous sommes d’accord : il y a des codes plus simples ! Mais en surchargeant la fonction ‘<<’ pour un nouveau type, on peut facilement l’écrire dans un stream.
IV. Conclusion sur les API
Si votre objectif est de lire ou d'écrire dans un fichier en ayant des performances maximales, évitez alors les streams STL et les std::string. Il vaut mieux privilégier l'API C couplée à des buffers statiques. J'ai bien conscience que c'est un coup de canif dans le contrat C++/Programmation orientée objet/STL, mais utiliser un outil au motif qu'il est facile d'usage sans vraiment comprendre ce qui se cache derrière est une source de risque.
« Lorsque le seul outil que l’on ait sous la main est un marteau, alors tous les problèmes finissent par ressembler à des clous » : soyez curieux, testez et comparez ! Ne prenez pas les choses pour acquises !