OVH Cloud OVH Cloud

performance de lecture de fichiers formatés

37 réponses
Avatar
Ploc
Bonjour,

j'ai un fichier du style :

label 2.3 4.5 5.6
label2 1.2 1.0 -2.
...


qui est assez gros (près d'1 Go).

J'avais un programme C à base de fscanf.
En passant de C (à base de fscanf) à c++ avec ifstream (voir le code en
bas), je passe de 1min 40s en C à 3min 10s en c++.

Je trouve que ca fait un gap quand même.
Comme je n'ai pas trop d'expérience sur les fichiers en c++, je m'en
remets à vous.

Vous pouvez me dire si vous voyez des choses à améliorer pour avoir de
meilleures performances (plus proches du C)?

Je joins un bout de code avec uniquement la lecture du fichier et
l'extraction des données.
Le code C fait la même chose mais avec fscanf et feof. Je peux le donner
si ca peut aider.


#include <iostream>
#include <fstream>

using namespace std;

int main(int argc, char ** argv)
{
ifstream ifs("fichier.toto");

float x,y,z;
string lc;

int count =0;
int i;
while (! ifs.eof())
{
ifs >> lc>>x>>y>>z;
if (!ifs.eof())
{
count ++;
}
}
cout <<"count = "<<count<<endl;

return 0;
}


Merci d'avance.

Ploc.

10 réponses

1 2 3 4
Avatar
Ploc
Fabien LE LEZ wrote:
On Sat, 03 May 2008 21:46:32 +0200, Ploc :

En passant de C (à base de fscanf) à c++ avec ifstream (voir le code en
bas), je passe de 1min 40s en C à 3min 10s en c++.


As-tu fait les mesures avec time (et, accessoirement, un top dans une
autre console) ? 10 Mo/s, ce n'est pas très loin de la vitesse
effective de lecture d'un fichier sur disque ; time (la fonction de
bash, pas celle de C ou C++) permet de savoir combien de temps le
processeur met effectivement pour décoder le fichier. Tu t'apercevras
peut-être que le programme en C++ est encore plus mauvais que tu le
croyais.


Les mesures ont été faites avec 'time ./mon_prog'. Et le temps donné est
le temps reel, et mesuré dans les mêmes conditions de charge et
d'ocupation mémoire.




while (! ifs.eof())
{
ifs >> lc>>x>>y>>z;
if (!ifs.eof())
{
count ++;
}
}


Le code ci-dessus me paraît louche. D'une part, tu ne devrais pas être
obligé d'appeler eof() deux fois dans la boucle. D'autre part, tu n'as
aucun contrôle d'erreur.

Si tu as l'assurance absolue que le fichier est écrit dans un format
précis, et qu'aucune erreur ne peut s'y glisser (ou que, en cas
d'erreur, un plantage ou un comportement erratique sont acceptables),
tu peux certes zapper certains tests, mais il faut le dire
explicitement.
En particulier, l'assurance absolue que la longueur de chaque ligne
est inférieure à une valeur fixée à l'avance, est indispensable pour
lire une chaîne de caractères avec fscanf (ou scanf). Sinon, il faut
utiliser fgets + sscanf.



Le bout de code que j'ai écrit est juste là pour des questions de test
que j'ai codé pour essayer d'expliquer les différences de rapidité entre
deux versions d'un programme réel. Mais la, ca n'a plus grand chose à
voir avec l'existant.
Ce n'est pas très génant pour nous de garder le code existant en C.

Mon intention ici est plus d'apprendre ce qui peut accélérer le
traitement de fichiers, ou au moins expliquer la différence de rapidité.



Si tu n'as pas besoin de la portabilité (ou si tu peux te permettre de
faire une version spéciale pour Windows), mmap() peut s'avérer une
piste à creuser pour améliorer les performances tout en réduisant les
problèmes.



Malheureusement, ca doit compiler (de préférence la même version) avec
visual studio.


Bien que j'utilise << avec plaisir, l'opérateur >> m'a toujours laissé
dubitatif. Du coup, généralement, je lis un fichier ligne par ligne
(avec std::getline()), puis j'analyse chaque ligne moi-même (y
compris, dans certains cas, avec sscanf). Mais je m'intéresse plus au
contrôle d'erreur qu'aux performances.


Le controle est important, mais si les perfs ne suivent pas, on ne va
probablement pas changer le code existant.

Ploc.


Avatar
Ploc
Fabien LE LEZ wrote:
On Sat, 03 May 2008 21:46:32 +0200, Ploc :

Le code C fait la même chose mais avec fscanf et feof. Je peux le donner
si ca peut aider.


Ce serait probablement une bonne idée, histoire d'avoir une référence
pour la performance.



Le voici.

#include <stdio.h>
#include <stdlib.h>


int main(int argc, char ** argv)
{
FILE *f=fopen("fichier.toto", "r");

float x,y,z;
char *lc=(char*)malloc(100 * sizeof(lc));

int count =0;

while (! feof(f))
{
fscanf(f, "%s %g %g %g", lc, &x, &y, &z);

if (! feof(f))
{
count ++;
}
}
fclose(f);
printf("count = %in", count);

return 0;
}


Avatar
Ploc
wrote:
On 3 mai, 21:46, Ploc wrote:
while (! ifs.eof())


une lecture ne se fait pas sur std::ios::eof()

je verrais plutot la lecture comme ca (non testé)
while(( ifs >> x >> y >> z ))
{}

cf FAQ C++ de developpez



C'est un peu mieux (de l'ordre de 4s).


Avatar
Fabien LE LEZ
On Sun, 04 May 2008 11:14:49 +0200, Ploc :

char *lc=(char*)malloc(100 * sizeof(lc));
[...]
fscanf(f, "%s %g %g %g", lc, &x, &y, &z);


Comme indiqué précédemment, si jamais la longueur de la chaîne
"labelXXX" est supérieure à 100, boum !

Avatar
Sylvain SF
Fabien LE LEZ wrote on 04/05/2008 17:08:
On Sun, 04 May 2008 11:14:49 +0200, Ploc :

char *lc=(char*)malloc(100 * sizeof(lc));
[...]
fscanf(f, "%s %g %g %g", lc, &x, &y, &z);


Comme indiqué précédemment, si jamais la longueur de la chaîne
"labelXXX" est supérieure à 100, boum !


fscanf_s(f, "%s %g %g %g", lc, 100, &x, &y, &z);

est supporté par gcc, non ?

Sylvain.


Avatar
espie
In article <481dd692$0$933$,
Sylvain SF wrote:
Fabien LE LEZ wrote on 04/05/2008 17:08:
On Sun, 04 May 2008 11:14:49 +0200, Ploc :

char *lc=(char*)malloc(100 * sizeof(lc));
[...]
fscanf(f, "%s %g %g %g", lc, &x, &y, &z);


Comme indiqué précédemment, si jamais la longueur de la chaîne
"labelXXX" est supérieure à 100, boum !


fscanf_s(f, "%s %g %g %g", lc, 100, &x, &y, &z);

est supporté par gcc, non ?


Non.

gcc est un compilateur.

La bibliotheque standard (et ses extensions) n'en font pas partie.



Avatar
Fabien LE LEZ
On Sun, 04 May 2008 17:30:31 +0200, "Sylvain SF"
:

est supporté par gcc, non ?


Mais du coup, ce n'est plus du C. Et si j'ai bien saisi, l'OP a besoin
de la portabilité.
(Par ailleurs, même si on accepte ce code, il faudra refaire le test
de performance.)

Avatar
Fabien LE LEZ
On Sun, 04 May 2008 11:14:43 +0200, Ploc :

Le bout de code que j'ai écrit est juste là pour des questions de test
que j'ai codé pour essayer d'expliquer les différences de rapidité entre
deux versions d'un programme réel. Mais la, ca n'a plus grand chose à
voir avec l'existant.


Ben justement, le code en C et le code en C++ ne font pas la même
chose. Difficile donc de les comparer.

Ce n'est pas très génant pour nous de garder le code existant en C.


Si le code existant fonctionne bien (ce qui n'est pas le cas du code C
que tu as fourni ici), effectivement, le changer ne sert pas à
grand-chose.

Avatar
Fabien LE LEZ
Après quelques essais, j'ai bien l'impression que parser soi-même les
données apporte un gain de performances assez confortable.


Ça se confirme. Et je me suis aperçu que mmap() ne change pas
grand-chose.

Le code que j'avais proposé, avec une fonction main() plus standard
(cf ci-dessous), donne d'aussi bons résultats.

Manifestement, au moins dans gcc, scanf est effroyablement lent, et >>
encore plus.

Seule autre modification : dans la série "cachez ce char const* que je
ne saurais voir", j'ai uniformisé pour n'utiliser que std::string,
même dans Source.

Voici donc un programme en C++ standard, certes un peu long, mais qui
est très largement plus rapide que ton code en C (et, a fortiori, que
ton code en C++).



#include <string>

class Source
{
public:
typedef std::string::const_iterator const_iterator;
bool Fini() const { return ptr == Fin(); }
void Avancer() { ++ptr; }
char Get() const { return *ptr; }
const_iterator Ptr() const { return ptr; }
const_iterator Fin() const { return src.end(); }

Source (std::string const& src_) : src (src_), ptr (src_.begin())
{}

private:
std::string const& src;
const_iterator ptr;
};

struct Destination
{
std::string label;
float a, b, c;
};

void RechercheDebutLigne (Source& src)
{
bool fin_ligne_trouvee= false;
while (!src.Fini())
{
if (src.Get()=='n')
{
fin_ligne_trouvee= true;
}
else if (fin_ligne_trouvee)
{
return;
}
src.Avancer();
}
}

bool Lire (float& dest, Source& src)
{
dest= 0;
bool negatif= false;
float decimale= 0.1;
bool info_rencontree= false;
bool virgule_rencontree= false;

while (!src.Fini())
{
if (src.Get() == 'n')
{
break;
}

if (src.Get() == '-')
{
if (info_rencontree)
{
return false; // mal formé
}
negatif= true;
}
else if (src.Get() == '.')
{
if (virgule_rencontree)
{
return false; // mal formé
}
virgule_rencontree= true;
info_rencontree= true;
}
else if (src.Get() >= '0' && src.Get() <= '9')
{
if (virgule_rencontree)
{
dest+= decimale * (src.Get()-'0');
decimale /= 10;
}
else
{
dest*= 10;
dest+= src.Get()-'0';
}
info_rencontree= true;
}
else if (info_rencontree)
{
break;
}
src.Avancer();
}

if (negatif)
{
dest= -dest;
}
return info_rencontree;
}

bool Lire (std::string& dest, Source& src)
{
Source::const_iterator debut= src.Fin();

while (!src.Fini())
{
if (src.Get() == 'n')
{
break;
}

if (src.Get() == ' ' || src.Get() == 't')
{
if (debut != src.Fin())
{
break;
}
}
else if (debut == src.Fin())
{
debut= src.Ptr();
}
src.Avancer();
}

if (debut == src.Fin())
{
return false;
}
else
{
dest.assign (debut, src.Ptr());
return true;
}
}


bool Lire (Destination& dest, Source& src)
{
bool ok= Lire (dest.label, src)
&& Lire (dest.a, src)
&& Lire (dest.b, src)
&& Lire (dest.c, src);
RechercheDebutLigne (src);
return ok;
}





#include <iostream>
#include <fstream>

int main()
{
using namespace std;
char const nom_src[]= "data.txt";
ifstream ifs (nom_src);

string ligne;
unsigned int num_ligne= 0;
while (std::getline (ifs, ligne, 'n'))
{
++num_ligne;
Destination d;
Source s (ligne);
if (!Lire (d, s))
{
// erreur
}
}
cerr << num_ligne << " lignes lues" << endl;
}

Avatar
Sylvain SF
Fabien LE LEZ wrote on 04/05/2008 17:40:

[fscanf_s] est supporté par gcc, non ?


Mais du coup, ce n'est plus du C.


qui, quoi ?

Et si j'ai bien saisi, l'OP a besoin de la portabilité.


ces _s sont supportés par VC 14+ (second compilo listé par le PO)
de nombreux posts (web) sur le net laissaient penser que gcc
(et ses libraries usuelles !...) les supportaient aussi.

si on accepte ce code, il faudra refaire le test de performance.


dans l'absolu oui, je commentais seulement le rique de BO.
(ça n'impliquait pas que ce serait a priori moins ou plus rapide).

Sylvain.


1 2 3 4