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;
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.
Précision importante - même machine, même charge - gcc et g++ v4.1.2 - mêmes options d'optimisation -O2 (même si ca ne semble pas beaucoup jouer ici).
Ploc wrote:
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.
Précision importante
- même machine, même charge
- gcc et g++ v4.1.2
- mêmes options d'optimisation -O2 (même si ca ne semble pas beaucoup
jouer ici).
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.
Précision importante - même machine, même charge - gcc et g++ v4.1.2 - mêmes options d'optimisation -O2 (même si ca ne semble pas beaucoup jouer ici).
Fabien LE LEZ
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.
On Sat, 03 May 2008 21:46:32 +0200, Ploc <ploc@clop.invalid>:
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 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.
Fabien LE LEZ
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.
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.
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.
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.
On Sat, 03 May 2008 21:46:32 +0200, Ploc <ploc@clop.invalid>:
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.
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.
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.
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.
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.
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.
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.
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.
Sylvain SF
Fabien LE LEZ wrote on 03/05/2008 22:37:
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
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.! (à la première lecture d'un fichier non caché - les lectures suivantes prennent 172 ms), écart mesuré avec _ftime sur un binaire (fopen("rb"))
le "temps de lecture effectif" a tout chance d'être négligeable par rapport au traitement de texte (scanf ou istream) qui est le problème ici.
[...] 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.
et tu as p.e. les 2.
Sylvain.
Fabien LE LEZ wrote on 03/05/2008 22:37:
On Sat, 03 May 2008 21:46:32 +0200, Ploc <ploc@clop.invalid>:
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
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
(à la première lecture d'un fichier non caché - les lectures suivantes
prennent 172 ms), écart mesuré avec _ftime sur un binaire (fopen("rb"))
le "temps de lecture effectif" a tout chance d'être négligeable par
rapport au traitement de texte (scanf ou istream) qui est le problème
ici.
[...] 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.
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
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.! (à la première lecture d'un fichier non caché - les lectures suivantes prennent 172 ms), écart mesuré avec _ftime sur un binaire (fopen("rb"))
le "temps de lecture effectif" a tout chance d'être négligeable par rapport au traitement de texte (scanf ou istream) qui est le problème ici.
[...] 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.
et tu as p.e. les 2.
Sylvain.
p-fifty-fifty
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
On 3 mai, 21:46, Ploc <p...@clop.invalid> 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 ))
{}
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la surface du disque.
Sylvain SF
Fabien LE LEZ wrote on 04/05/2008 01:05:
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la surface du disque.
pour être honnête j'étais sous les 3 sec. (2.6); je ne pense pas qu'un fichier à la périphérie prendra 10 fois le temps d'un fichier près du centre - ce faisant je n'imagine pas que l'on parle d'un disk de +5 ans d'age.
je répète le point qui me semble "time consuming" et qui donc devrait être imho le point d'intérêt : la conversion texte en float.
la lecture binaire est rapide - ok sauf en PATA de base avec un disk poussif.
Sylvain.
Fabien LE LEZ wrote on 04/05/2008 01:05:
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la
surface du disque.
pour être honnête j'étais sous les 3 sec. (2.6); je ne pense pas qu'un
fichier à la périphérie prendra 10 fois le temps d'un fichier près du
centre - ce faisant je n'imagine pas que l'on parle d'un disk de +5 ans
d'age.
je répète le point qui me semble "time consuming" et qui donc devrait
être imho le point d'intérêt : la conversion texte en float.
la lecture binaire est rapide - ok sauf en PATA de base avec un disk
poussif.
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la surface du disque.
pour être honnête j'étais sous les 3 sec. (2.6); je ne pense pas qu'un fichier à la périphérie prendra 10 fois le temps d'un fichier près du centre - ce faisant je n'imagine pas que l'on parle d'un disk de +5 ans d'age.
je répète le point qui me semble "time consuming" et qui donc devrait être imho le point d'intérêt : la conversion texte en float.
la lecture binaire est rapide - ok sauf en PATA de base avec un disk poussif.
Sylvain.
Sylvain SF
Fabien LE LEZ wrote on 04/05/2008 01:05:
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la surface du disque.
2.8 sec. (pas .6) sur la 1ière partition, 5.1 sec. sur la 4ième et dernière, ça influe ... d'un facteur <2 pas de 6.
Sylvain.
Fabien LE LEZ wrote on 04/05/2008 01:05:
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la
surface du disque.
2.8 sec. (pas .6) sur la 1ière partition, 5.1 sec. sur la 4ième
et dernière, ça influe ... d'un facteur <2 pas de 6.
On Sun, 04 May 2008 00:17:55 +0200, "Sylvain SF" :
pas vraiment, avec fread et un buffer de 64Ko, je lis 180Mo en 3 sec.!
Ça m'étonnerait beaucoup que tu aies une telle vitesse sur toute la surface du disque.
2.8 sec. (pas .6) sur la 1ière partition, 5.1 sec. sur la 4ième et dernière, ça influe ... d'un facteur <2 pas de 6.
Sylvain.
Fabien LE LEZ
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++.
Après quelques essais, j'ai bien l'impression que parser soi-même les données apporte un gain de performances assez confortable.
Soit donc un
struct Destination { std::string label; float a, b, c; };
que je remplis par lecture d'une ligne dans le fichier, puis que je jette sans rien en faire. Recommencer jusqu'à épuisement du fichier. En cas d'erreur de format, on considère que la ligne est déficiente, et on passe à la suivante.
J'ai fait les tests sur un AMD Athlon64 double coeur, 2,4 GHz, avec 2 Go de RAM. Debian 64 bits, g++ 4.1.2, optimisation "-O3".
Commençons par un fichier de 953 Mo. real 0m24.446s user 0m11.129s sys 0m0.412s
953.674 Mo une deuxième fois : real 0m13.218s user 0m11.277s sys 0m0.372s
La deuxième fois, le fichier était déjà en cache, ce qui semble confirmer que la ligne "user" représente bien le temps que le processeur met à convertir les données.
Un fichier un peu plus gros (3814 Mo) confirme une vitesse de traitement d'environ 85 Mo/s : real 2m34.111s user 0m45.239s sys 0m3.844s
Si maintenant je mets en commentaire la ligne 134 (i.e. je supprime l'appel à std::string::assign()), j'obtiens, pour mon fichier de 3814 Mo :
real 2m5.296s user 0m17.713s sys 0m3.844s
soit 2,5 fois moins. La recopie de la chaîne de caractères "labelXXX" est donc, de loin, ce qui prend le plus de temps.
C'est bon à savoir : si jamais tu n'as besoin de ce texte que de temps en temps, tu peux te contenter de conserver les pointeurs sur le début et la fin de la chaîne (la fonction mmap() garantit que la mémoire pointée sera toujours accessible). Et si ce texte est juste là pour faire joli, et ne sert à rien, tu peux carrément ne pas conserver sa valeur.
Avertissement : le code ci-dessous est avant tout un code de test, conçu pour rechercher la méthode la plus rapide. Même s'il m'a l'air de fonctionner correctement, et comporte du code de détection d'erreur (sauf dans main()), il ne s'agit pas de code de production.
char const nom_src[]= "data.txt"; unsigned int const taille_src= 4000000000; int fichier= open (nom_src, O_RDONLY); void* v_data= mmap (0, taille_src, PROT_READ, MAP_PRIVATE, fichier, 0); assert (v_data!=MAP_FAILED); char const* data= static_cast<char const*>(v_data);
Source src (data, data + taille_src); for (int num_ligne=0; !src.Fini(); ++num_ligne) { Destination d; if (!Lire (d, src)) { //cerr << "Erreur ligne " << num_ligne << endl; } }
double taille_en_Mo= taille_src/1024.0/1024.0; cerr << taille_en_Mo << " Mo" << endl; }/* C'est la fin du programme : le fichier est automatiquement fermé ; il n'y a pas eu d'écritures, donc on n'a pas à se soucier de savoir si la fermeture a réussi. */
On Sat, 03 May 2008 21:46:32 +0200, Ploc <ploc@clop.invalid>:
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++.
Après quelques essais, j'ai bien l'impression que parser soi-même les
données apporte un gain de performances assez confortable.
Soit donc un
struct Destination
{
std::string label;
float a, b, c;
};
que je remplis par lecture d'une ligne dans le fichier, puis que je
jette sans rien en faire. Recommencer jusqu'à épuisement du fichier.
En cas d'erreur de format, on considère que la ligne est déficiente,
et on passe à la suivante.
J'ai fait les tests sur un AMD Athlon64 double coeur, 2,4 GHz, avec
2 Go de RAM. Debian 64 bits, g++ 4.1.2, optimisation "-O3".
Commençons par un fichier de 953 Mo.
real 0m24.446s
user 0m11.129s
sys 0m0.412s
953.674 Mo une deuxième fois :
real 0m13.218s
user 0m11.277s
sys 0m0.372s
La deuxième fois, le fichier était déjà en cache, ce qui semble
confirmer que la ligne "user" représente bien le temps que le
processeur met à convertir les données.
Un fichier un peu plus gros (3814 Mo) confirme une vitesse de
traitement d'environ 85 Mo/s :
real 2m34.111s
user 0m45.239s
sys 0m3.844s
Si maintenant je mets en commentaire la ligne 134 (i.e. je supprime
l'appel à std::string::assign()), j'obtiens, pour mon fichier de
3814 Mo :
real 2m5.296s
user 0m17.713s
sys 0m3.844s
soit 2,5 fois moins. La recopie de la chaîne de caractères "labelXXX"
est donc, de loin, ce qui prend le plus de temps.
C'est bon à savoir : si jamais tu n'as besoin de ce texte que de temps
en temps, tu peux te contenter de conserver les pointeurs sur le début
et la fin de la chaîne (la fonction mmap() garantit que la mémoire
pointée sera toujours accessible). Et si ce texte est juste là pour
faire joli, et ne sert à rien, tu peux carrément ne pas conserver sa
valeur.
Avertissement : le code ci-dessous est avant tout un code de test,
conçu pour rechercher la méthode la plus rapide.
Même s'il m'a l'air de fonctionner correctement, et comporte du code
de détection d'erreur (sauf dans main()), il ne s'agit pas de code de
production.
char const nom_src[]= "data.txt";
unsigned int const taille_src= 4000000000;
int fichier= open (nom_src, O_RDONLY);
void* v_data= mmap
(0, taille_src, PROT_READ, MAP_PRIVATE, fichier, 0);
assert (v_data!=MAP_FAILED);
char const* data= static_cast<char const*>(v_data);
Source src (data, data + taille_src);
for (int num_ligne=0; !src.Fini(); ++num_ligne)
{
Destination d;
if (!Lire (d, src))
{
//cerr << "Erreur ligne " << num_ligne << endl;
}
}
double taille_en_Mo= taille_src/1024.0/1024.0;
cerr << taille_en_Mo << " Mo" << endl;
}/* C'est la fin du programme : le fichier est automatiquement fermé ;
il n'y a pas eu d'écritures, donc on n'a pas à se soucier de savoir si
la fermeture a réussi. */
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++.
Après quelques essais, j'ai bien l'impression que parser soi-même les données apporte un gain de performances assez confortable.
Soit donc un
struct Destination { std::string label; float a, b, c; };
que je remplis par lecture d'une ligne dans le fichier, puis que je jette sans rien en faire. Recommencer jusqu'à épuisement du fichier. En cas d'erreur de format, on considère que la ligne est déficiente, et on passe à la suivante.
J'ai fait les tests sur un AMD Athlon64 double coeur, 2,4 GHz, avec 2 Go de RAM. Debian 64 bits, g++ 4.1.2, optimisation "-O3".
Commençons par un fichier de 953 Mo. real 0m24.446s user 0m11.129s sys 0m0.412s
953.674 Mo une deuxième fois : real 0m13.218s user 0m11.277s sys 0m0.372s
La deuxième fois, le fichier était déjà en cache, ce qui semble confirmer que la ligne "user" représente bien le temps que le processeur met à convertir les données.
Un fichier un peu plus gros (3814 Mo) confirme une vitesse de traitement d'environ 85 Mo/s : real 2m34.111s user 0m45.239s sys 0m3.844s
Si maintenant je mets en commentaire la ligne 134 (i.e. je supprime l'appel à std::string::assign()), j'obtiens, pour mon fichier de 3814 Mo :
real 2m5.296s user 0m17.713s sys 0m3.844s
soit 2,5 fois moins. La recopie de la chaîne de caractères "labelXXX" est donc, de loin, ce qui prend le plus de temps.
C'est bon à savoir : si jamais tu n'as besoin de ce texte que de temps en temps, tu peux te contenter de conserver les pointeurs sur le début et la fin de la chaîne (la fonction mmap() garantit que la mémoire pointée sera toujours accessible). Et si ce texte est juste là pour faire joli, et ne sert à rien, tu peux carrément ne pas conserver sa valeur.
Avertissement : le code ci-dessous est avant tout un code de test, conçu pour rechercher la méthode la plus rapide. Même s'il m'a l'air de fonctionner correctement, et comporte du code de détection d'erreur (sauf dans main()), il ne s'agit pas de code de production.
char const nom_src[]= "data.txt"; unsigned int const taille_src= 4000000000; int fichier= open (nom_src, O_RDONLY); void* v_data= mmap (0, taille_src, PROT_READ, MAP_PRIVATE, fichier, 0); assert (v_data!=MAP_FAILED); char const* data= static_cast<char const*>(v_data);
Source src (data, data + taille_src); for (int num_ligne=0; !src.Fini(); ++num_ligne) { Destination d; if (!Lire (d, src)) { //cerr << "Erreur ligne " << num_ligne << endl; } }
double taille_en_Mo= taille_src/1024.0/1024.0; cerr << taille_en_Mo << " Mo" << endl; }/* C'est la fin du programme : le fichier est automatiquement fermé ; il n'y a pas eu d'écritures, donc on n'a pas à se soucier de savoir si la fermeture a réussi. */
Fabien LE LEZ
tu peux te contenter de conserver les pointeurs sur le début et la fin de la chaîne (la fonction mmap() garantit que la mémoire pointée sera toujours accessible).
En fait, tu peux même remplacer std::string par une "chaîne du pauvre", moins riche en fonctionnalités mais plus rapide dans ce cas précis :
class StringDuPauvre { public: void assign (char const* debut_, unsigned int longueur_) { debut= debut_, longueur= longueur_; }
struct Destination { String label; float a, b, c; };
(La durée de vie des tableaux pointés est gérée par mmap().)
Comme prévu, j'obtiens une vitesse de calcul deux fois plus élevée.
tu peux te contenter de conserver les pointeurs sur le début
et la fin de la chaîne (la fonction mmap() garantit que la mémoire
pointée sera toujours accessible).
En fait, tu peux même remplacer std::string par une "chaîne du
pauvre", moins riche en fonctionnalités mais plus rapide dans ce cas
précis :
class StringDuPauvre
{
public:
void assign (char const* debut_, unsigned int longueur_)
{ debut= debut_, longueur= longueur_; }
tu peux te contenter de conserver les pointeurs sur le début et la fin de la chaîne (la fonction mmap() garantit que la mémoire pointée sera toujours accessible).
En fait, tu peux même remplacer std::string par une "chaîne du pauvre", moins riche en fonctionnalités mais plus rapide dans ce cas précis :
class StringDuPauvre { public: void assign (char const* debut_, unsigned int longueur_) { debut= debut_, longueur= longueur_; }