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;
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.
< snip code de test >
J'ai finalement pu tester ce programme. Tel quel il est assez proche en durée de la version c. En sortant la ligne 'Destination d' de la boucle, on fait même mieux (on teste toujours le temps d'I/O, pour l'utilisation des valeurs extraites, on verra plus tard). Par contre, le mmap passera jamais sous visual studio.
La remarque sur l'extraction des labels est très interressante. Malheureusement, j'en ai besion assez vite. Dommage.
Fabien LE LEZ wrote:
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.
< snip code de test >
J'ai finalement pu tester ce programme.
Tel quel il est assez proche en durée de la version c.
En sortant la ligne 'Destination d' de la boucle, on fait même mieux (on
teste toujours le temps d'I/O, pour l'utilisation des valeurs extraites,
on verra plus tard).
Par contre, le mmap passera jamais sous visual studio.
La remarque sur l'extraction des labels est très interressante.
Malheureusement, j'en ai besion assez vite. Dommage.
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.
< snip code de test >
J'ai finalement pu tester ce programme. Tel quel il est assez proche en durée de la version c. En sortant la ligne 'Destination d' de la boucle, on fait même mieux (on teste toujours le temps d'I/O, pour l'utilisation des valeurs extraites, on verra plus tard). Par contre, le mmap passera jamais sous visual studio.
La remarque sur l'extraction des labels est très interressante. Malheureusement, j'en ai besion assez vite. Dommage.
Fabien LE LEZ
On Sun, 04 May 2008 19:18:40 +0200, Ploc :
Tel quel il est assez proche en durée de la version c.
Bizarre. J'ai une différence d'un facteur 4 ou 5. (Sauf en l'absence d'optimisation ("-Ox"))
Par contre, le mmap passera jamais sous visual studio.
Il y a l'équivalent presque exact sous Windows. De toute façon je t'ai donné une autre version, en C++ standard.
On Sun, 04 May 2008 19:18:40 +0200, Ploc <ploc@clop.invalid>:
Tel quel il est assez proche en durée de la version c.
Bizarre. J'ai une différence d'un facteur 4 ou 5.
(Sauf en l'absence d'optimisation ("-Ox"))
Par contre, le mmap passera jamais sous visual studio.
Il y a l'équivalent presque exact sous Windows.
De toute façon je t'ai donné une autre version, en C++ standard.
La remarque sur l'extraction des labels est très interressante.
Qu'est-ce que tu dois en faire exactement, de ces labels ?
Ploc
Sylvain SF wrote:
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.
Malheureusement, le visual studio qu'on nous impose est loin d'etre aussi récent...
Sylvain SF wrote:
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.
Malheureusement, le visual studio qu'on nous impose est loin d'etre
aussi récent...
[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.
Malheureusement, le visual studio qu'on nous impose est loin d'etre aussi récent...
Ploc
Fabien LE LEZ wrote:
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++).
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
J'aurais cru que les ifstream seraient plus rapides. En tout cas, je pense que si on décide de passer cette partie en c++, on parsera le fichier 'à la main'.
Merci pour les conseils.
Ploc.
Fabien LE LEZ wrote:
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++).
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7
Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
J'aurais cru que les ifstream seraient plus rapides.
En tout cas, je pense que si on décide de passer cette partie en c++, on
parsera le fichier 'à la main'.
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++).
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
J'aurais cru que les ifstream seraient plus rapides. En tout cas, je pense que si on décide de passer cette partie en c++, on parsera le fichier 'à la main'.
Merci pour les conseils.
Ploc.
Ploc
Fabien LE LEZ wrote:
On Sun, 04 May 2008 19:18:40 +0200, Ploc :
La remarque sur l'extraction des labels est très interressante.
Qu'est-ce que tu dois en faire exactement, de ces labels ?
Dans le format original qui est un peu plus complexe, les lignes peuvent contenir des références à d'autres labels définis en amont pour construire une structure d'arbre.
Fabien LE LEZ wrote:
On Sun, 04 May 2008 19:18:40 +0200, Ploc <ploc@clop.invalid>:
La remarque sur l'extraction des labels est très interressante.
Qu'est-ce que tu dois en faire exactement, de ces labels ?
Dans le format original qui est un peu plus complexe, les lignes peuvent
contenir des références à d'autres labels définis en amont pour
construire une structure d'arbre.
La remarque sur l'extraction des labels est très interressante.
Qu'est-ce que tu dois en faire exactement, de ces labels ?
Dans le format original qui est un peu plus complexe, les lignes peuvent contenir des références à d'autres labels définis en amont pour construire une structure d'arbre.
Fabien LE LEZ
On Sun, 04 May 2008 19:53:16 +0200, Ploc :
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation utilises-tu ? J'obtiens un temps comparable pour ta version en C (79 secondes), mais un temps bien plus réduit pour ma version (24 secondes). Par contre, sans optimisation, mon programme est impressionnant de nullité : 2 minutes 40 secondes ! Le fait que l'optimisation change considérablement le temps d'exécution est d'ailleurs compréhensible, puisque le code fait tout le boulot, quasiment sans appel de fonctions externes.
On Sun, 04 May 2008 19:53:16 +0200, Ploc <ploc@clop.invalid>:
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7
Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation
utilises-tu ?
J'obtiens un temps comparable pour ta version en C (79 secondes), mais
un temps bien plus réduit pour ma version (24 secondes).
Par contre, sans optimisation, mon programme est impressionnant de
nullité : 2 minutes 40 secondes !
Le fait que l'optimisation change considérablement le temps
d'exécution est d'ailleurs compréhensible, puisque le code fait tout
le boulot, quasiment sans appel de fonctions externes.
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation utilises-tu ? J'obtiens un temps comparable pour ta version en C (79 secondes), mais un temps bien plus réduit pour ma version (24 secondes). Par contre, sans optimisation, mon programme est impressionnant de nullité : 2 minutes 40 secondes ! Le fait que l'optimisation change considérablement le temps d'exécution est d'ailleurs compréhensible, puisque le code fait tout le boulot, quasiment sans appel de fonctions externes.
Sylvain SF
Ploc wrote on 04/05/2008 19:39:
Malheureusement, le visual studio qu'on nous impose est loin d'etre aussi récent...
cl.exe version 14 n'est "que" VS 2005, cela devait être présent dans la version 2003 également, les versions express (gratuites) peuvent gérer certains cas, mais qu'importe puisque _s est non conforme.
Sylvain.
Ploc wrote on 04/05/2008 19:39:
Malheureusement, le visual studio qu'on nous impose est loin d'etre
aussi récent...
cl.exe version 14 n'est "que" VS 2005, cela devait être présent dans
la version 2003 également, les versions express (gratuites) peuvent
gérer certains cas, mais qu'importe puisque _s est non conforme.
Malheureusement, le visual studio qu'on nous impose est loin d'etre aussi récent...
cl.exe version 14 n'est "que" VS 2005, cela devait être présent dans la version 2003 également, les versions express (gratuites) peuvent gérer certains cas, mais qu'importe puisque _s est non conforme.
Sylvain.
Ploc
Fabien LE LEZ wrote:
On Sun, 04 May 2008 19:53:16 +0200, Ploc :
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation utilises-tu ? J'obtiens un temps comparable pour ta version en C (79 secondes), mais un temps bien plus réduit pour ma version (24 secondes). Par contre, sans optimisation, mon programme est impressionnant de nullité : 2 minutes 40 secondes ! Le fait que l'optimisation change considérablement le temps d'exécution est d'ailleurs compréhensible, puisque le code fait tout le boulot, quasiment sans appel de fonctions externes.
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres hdparm -t). Pour la compilation, c'est tout du -O2. Il est clair que dans mon cas, ma machine de dev bloque au niveau du debit disque + memoire. Mais je le sais et j'attendais des perfs comparables au C, sans forcément les éclater comme c'est le cas ici.
Sans optimisation, j'obtiens >= 3min 40s.
Fabien LE LEZ wrote:
On Sun, 04 May 2008 19:53:16 +0200, Ploc <ploc@clop.invalid>:
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7
Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation
utilises-tu ?
J'obtiens un temps comparable pour ta version en C (79 secondes), mais
un temps bien plus réduit pour ma version (24 secondes).
Par contre, sans optimisation, mon programme est impressionnant de
nullité : 2 minutes 40 secondes !
Le fait que l'optimisation change considérablement le temps
d'exécution est d'ailleurs compréhensible, puisque le code fait tout
le boulot, quasiment sans appel de fonctions externes.
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres
hdparm -t).
Pour la compilation, c'est tout du -O2.
Il est clair que dans mon cas, ma machine de dev bloque au niveau du
debit disque + memoire. Mais je le sais et j'attendais des perfs
comparables au C, sans forcément les éclater comme c'est le cas ici.
C'est effectivement très rapide : de l'ordre d' 1min 6s chez moi (37.7 Millions de lignes, 1.3Go) contre 1min 35s dans la version c.
Quel type de machine as-tu, et quelles options de compilation utilises-tu ? J'obtiens un temps comparable pour ta version en C (79 secondes), mais un temps bien plus réduit pour ma version (24 secondes). Par contre, sans optimisation, mon programme est impressionnant de nullité : 2 minutes 40 secondes ! Le fait que l'optimisation change considérablement le temps d'exécution est d'ailleurs compréhensible, puisque le code fait tout le boulot, quasiment sans appel de fonctions externes.
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres hdparm -t). Pour la compilation, c'est tout du -O2. Il est clair que dans mon cas, ma machine de dev bloque au niveau du debit disque + memoire. Mais je le sais et j'attendais des perfs comparables au C, sans forcément les éclater comme c'est le cas ici.
Sans optimisation, j'obtiens >= 3min 40s.
Fabien LE LEZ
On Sun, 04 May 2008 23:18:19 +0200, Ploc :
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres hdparm -t).
J'ai 2 Go, ce qui explique que le fichier tienne en cache chez moi et pas chez toi. Mais...
Il est clair que dans mon cas, ma machine de dev bloque au niveau du debit disque + memoire. Mais je le sais et j'attendais des perfs comparables au C,
...justement, time affiche (sur sa deuxième ligne) le temps processeur réellement utilisé par le programme, ce qui, en cas d'E/S lentes, est très inférieur à la durée entre le début et la fin de l'exécution.
On Sun, 04 May 2008 23:18:19 +0200, Ploc <ploc@clop.invalid>:
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres
hdparm -t).
J'ai 2 Go, ce qui explique que le fichier tienne en cache chez moi et
pas chez toi. Mais...
Il est clair que dans mon cas, ma machine de dev bloque au niveau du
debit disque + memoire. Mais je le sais et j'attendais des perfs
comparables au C,
...justement, time affiche (sur sa deuxième ligne) le temps processeur
réellement utilisé par le programme, ce qui, en cas d'E/S lentes, est
très inférieur à la durée entre le début et la fin de l'exécution.
Athlon XP 2500+/ 1Go RAM + DD un peu poussif (debit à 40Mo/s d'apres hdparm -t).
J'ai 2 Go, ce qui explique que le fichier tienne en cache chez moi et pas chez toi. Mais...
Il est clair que dans mon cas, ma machine de dev bloque au niveau du debit disque + memoire. Mais je le sais et j'attendais des perfs comparables au C,
...justement, time affiche (sur sa deuxième ligne) le temps processeur réellement utilisé par le programme, ce qui, en cas d'E/S lentes, est très inférieur à la durée entre le début et la fin de l'exécution.