Re: performance de lecture de fichiers formatés
  Home FAQ Contact Sign in
fr.comp.lang.c++ only
 
Advanced search
POPULAR GROUPS

more...

 Up
Re: performance de lecture de fichiers formatés         

Group: fr.comp.lang.c++ · Group Profile
Author: Fabien LE LEZ
Date: May 3, 2008 18:58

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.

#include

class Source
{
public:
bool Fini() const { return ptr == fin; }
void Avancer() { ++ptr; }
char Get() const { return *ptr; }
char const* Ptr() const { return ptr; }

public:
Source (char const* debut, char const* fin_)
: ptr (debut), fin (fin_) {}

private:
char const* ptr;
char const* const fin;
};

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)
{
char const* debut= 0;

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

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

if (debut == 0)
{
return false;
}
else
{
dest.assign (debut, src.Ptr()-debut);
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
#include
#include
#include

int main()
{
using namespace std;

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(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. */
no comments
diggit! del.icio.us! reddit!