IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

buffer "par morceau" pour gros fichiers binaires


Sujet :

Langage C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Février 2006
    Messages
    127
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 127
    Points : 49
    Points
    49
    Par défaut buffer "par morceau" pour gros fichiers binaires
    Bonjour,

    Dans mon application, je lis des fichiers binaires volumineux (de 100 à 400 mo). Ceux-ci contiennent des objets sérialisés relativement petits mais en très grands nombres (quelques millions d'objets de 16 ou 20 octets). La structure du fichier est la suivante:

    nb1[unsigned int] nb2[unsigned int] nb3[unsigned int]
    obj1[20 octets]
    ...
    obji[20 octets]
    ...
    objnb2[20 octets]
    Actuellement, pour lire le fichier, j'utilise un ifstream.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    lireFichier(ifstream &in)
    {
      	  lireBinaireCplx(in, nb1);
      	  lireBinaireCplx(in, nb2);
      	  lireBinaireCplx(in, nb3);
     
    	objSerialise obj;
    	vector<objSerialise > vect;
     
    	for (unsigned int i=0; i<nb2; i++){
    		lireBinaireCplx(in, obj);
    		vect.push_back(obj);
    	}
     
    }
    avec

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template < typename type_val >
    inline void lireBinaireCplx(ifstream& in, type_val& v)
    {
                in.read((char*)&v, sizeof(v));
    }
    Cependant, la lecture est très lente: un fichier de 100 mo est lu en 20 secondes. Lorsque je relance mon application, la lecture ne prend plus que 1,2 secondes, sans doute parce que le fichier est conservé en mémoire par windows.

    D'où ma question: Comment accélérer la lecture de mon fichier? Je pense le charger entièrement en mémoire dans un buffer au début de la fonction lireFichier. Cependant, ce fichier étant très volumineux, puis-je le charger en plusieurs morceaux (paquets de 50 mo par exemple)? J'ai lu la FAQ développer sur la lecture/écriture de fichier et je n'ai pas trouvé de solution concernant un buffer pour les fichiers binaires. Avez-vous des suggestions de solutions.

    Merci par avance,
    Cordialement,
    Benoît

  2. #2
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Quelques idées jetées en l'air mais à voir si ça a un quelconque impact :
    -> remplacer ta boucle par un resize/read
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    lireFichier(ifstream &in)
    {
      	  lireBinaireCplx(in, nb1);
      	  lireBinaireCplx(in, nb2);
      	  lireBinaireCplx(in, nb3);
     
    	objSerialise obj;
    	vector<objSerialise > vect;
     
            vect.resize(nb2);// si le cout de construction n'est pas élevé
            lireBinaireCplx(in, &vect[0],nb2); 
    }
    template < typename type_val >
    inline void lireBinaireCplx(ifstream& in, type_val* p_v, size_t nbr_)
    {
                in.read(reinterpret_cast<char*>(p_v), sizeof(type_val)*nbr_);
    }
    2/ Jouer avec ifstream.rdbuf->pubsetbuf() en lui donnant un buffer conséquent
    3/ Regarder du côté de Boost.IOStreams (voir ici)
    4/ Regarder du côte d'un file mapping soit en fonction de ton OS soit avec Boost.Interprocess.

    A part la première solution, je ne sais pas à priori si les autres sont des pistes viables. Faudrait regarder plus en détail les implications et faire des tests.

    P.S. : pourquoi t'as marqué ton sujet comme résolu ?

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Février 2006
    Messages
    127
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 127
    Points : 49
    Points
    49
    Par défaut
    merci pour votre réponse, excusez-moi du délais de ma réponse, votre première solutions a accéléré la lecture de mes données de manière acceptable ,

    Cordialement,

  4. #4
    Invité
    Invité(e)
    Par défaut
    Si tu es sous Windows, le plus rapide est d'utiliser des FileMapping.

    Voici le genre de chose que j'utilise (tu peux avoir à changer certains paramètres...)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     
    class TFileMapping {
          HANDLE FileHandle;
          HANDLE MapHandle;
          LPVOID MapPtr;
    public :
          unsigned char *Data;
          unsigned long Length;
          unsigned long SuperLength;
     
          TFileMapping() {Data=NULL;}
          void Open(string Name);
          void Close(void);
     
    };
     
    void TFileMapping::Open(string Name) {
            FileHandle=CreateFile(Name.c_str(),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS,NULL);
            MapHandle=CreateFileMapping(FileHandle,NULL,PAGE_READONLY,0,0,NULL);
            MapPtr=MapViewOfFile(MapHandle,FILE_MAP_READ,0,0,0);
            Length=GetFileSize(FileHandle,&SuperLength);
            Data=(unsigned char*) MapPtr;
    }
     
    void TFileMapping::Close(void) {
            UnmapViewOfFile(MapPtr);
            CloseHandle(MapHandle);
            CloseHandle(FileHandle);
            Data=NULL;
    }
    Une fois ouvert, ton fichier se comporte comme un tableau en mémoire... C'est désespérément simple.

    Ca marche en local et en réseau, mais en local, je doute qu'il y ait plus rapide (sous Windows).

    Francois

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Février 2006
    Messages
    127
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 127
    Points : 49
    Points
    49
    Par défaut
    Merci à tous pour vos réponse, pour la lecture de mon fichier, je suis maintenant simplement limité par le débit du disque. C'est très acceptable, le gain avec le nouvel algorithme de lecture étant environ un facteur 10 !

    J'ai utilisé la première solution de 3DArchi, en "découpant" la lecture:

    Je lis de l'objet 1 à l'objet nb2/i puis de l'objet nb2/i + 1 à nb2/2*i ... jusqu'à nb2... Je choisis i dynamiquement en fonction de la taille du fichier de telle sorte que le vecteur qui me sert de buffer ne dépasse pas 100 mo en mémoire. En fait, j'ai à traiter des fichiers dépassant le Go et de fait, si je le charge d'une seule traite en mémoire, j'ai un joli std::bad_alloc sur des machines 32 bits.

    >fcharton: Je testerais à l'occasion votre solution mais pour cette application, le code se doit d'être portable (compilé avec gcc sous Unix et le portage MinGw sous windows).

    Cordialement,

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Benoit_T Voir le message
    >fcharton: Je testerais à l'occasion votre solution mais pour cette application, le code se doit d'être portable (compilé avec gcc sous Unix et le portage MinGw sous windows).
    L'idée du file mapping (qui était ma proposition 4 ) peut être mise en oeuvre de façon portable avec Boost. Mais ensuite, faudrait bencher pour voir si tu as vraiment du gain par rapport à ce que tu as déjà fait.

  7. #7
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    L'idée du file mapping (qui était ma proposition 4 ) peut être mise en oeuvre de façon portable avec Boost. Mais ensuite, faudrait bencher pour voir si tu as vraiment du gain par rapport à ce que tu as déjà fait.
    Effectivement, tout dépend comment Boost l'implémente (sous Windows)...

    Sous Windows, le FileMapping va très vite parce que c'est une fonction de bas niveau du système (en fait c'est ce que Windows utilise pour lui même...). Apparemment sous Unix et Linux, on a une fonction mmap qui remplit le même role, mais je ne sais pas comment elle est mise en oeuvre...

    Le point important, à mon avis, c'est que les accès disques rapides sont un des domaines où l'on doit rester proche de la machine, et donc souvent avoir un code par machine cible... si la librairie ne le fait pas. Et ce n'est, hélas, pas le cas de la librairie iostream (ou du moins des implémentations dont je dispose)...

    Francois

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Citation Envoyé par fcharton Voir le message
    Le point important, à mon avis, c'est que les accès disques rapides sont un des domaines où l'on doit rester proche de la machine, et donc souvent avoir un code par machine cible... si la librairie ne le fait pas. Et ce n'est, hélas, pas le cas de la librairie iostream (ou du moins des implémentations dont je dispose)...
    Globalement, j'ai tendance à penser la même chose. Sauf qu'ici, si j'ai bien compris son noeud était dans des petites lectures successives et il a pu trouver une solution satisfaisante en augmentant la taille de chaque lecture. Donc, des fois, faut pas chercher trop loin.

  9. #9
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Citation Envoyé par fcharton Voir le message
    Si tu es sous Windows, le plus rapide est d'utiliser des FileMapping.

    Voici le genre de chose que j'utilise (tu peux avoir à changer certains paramètres...)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     
    class TFileMapping {
          HANDLE FileHandle;
          HANDLE MapHandle;
          LPVOID MapPtr;
    public :
          unsigned char *Data;
          unsigned long Length;
          unsigned long SuperLength;
     
          TFileMapping() {Data=NULL;}
          void Open(string Name);
          void Close(void);
     
    };
     
    void TFileMapping::Open(string Name) {
            FileHandle=CreateFile(Name.c_str(),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS,NULL);
            MapHandle=CreateFileMapping(FileHandle,NULL,PAGE_READONLY,0,0,NULL);
            MapPtr=MapViewOfFile(MapHandle,FILE_MAP_READ,0,0,0);
            Length=GetFileSize(FileHandle,&SuperLength);
            Data=(unsigned char*) MapPtr;
    }
     
    void TFileMapping::Close(void) {
            UnmapViewOfFile(MapPtr);
            CloseHandle(MapHandle);
            CloseHandle(FileHandle);
            Data=NULL;
    }
    Une fois ouvert, ton fichier se comporte comme un tableau en mémoire... C'est désespérément simple.

    Ca marche en local et en réseau, mais en local, je doute qu'il y ait plus rapide (sous Windows).

    Francois
    Je ne vois pas comment ça marche ce truc.
    Si ça charge le fichier en entier en mémoire je ne vois pas l'intérêt (et un ReadFile est bien plus simple).
    S'il faut commencer à gérer des pages d'accès soi-même je ne vois pas en quoi c'est désespérément simple (quid des fichiers dont la taille est >4Gb ?).
    Sans compter la gestion d'erreur qui semble assez lourde (à la lecture de la doc de MS en tout cas).

    Mais bon, je ne connais pas, et j'ai beau lire la doc de MS, je ne comprends pas comment utiliser ça avec des fichiers (sauf pour faire du shared memory entre process où là, en effet, ça semble simple).

  10. #10
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    L'intérêt des mapping mémoire, c'est qu'ils peuvent s'effectuer par page de manière paresseuse.
    Boost ftw

  11. #11
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par camboui Voir le message
    Je ne vois pas comment ça marche ce truc.
    Le mapping mémoire, ça consiste à indiquer à Windows qu'une zone du système de fichier va être fréquemment accédée. La gestion réelle des lectures et des écritures est faite par le système de pagination de l'OS (qui gère la mémoire virtuelle). Ca va vite parce que c'est de très bas niveau. Windows utilise cela pour les éxécutables, les ressources système, les pages mémoires qu'il stocke sur le disque...

    Citation Envoyé par camboui Voir le message
    Si ça charge le fichier en entier en mémoire je ne vois pas l'intérêt (et un ReadFile est bien plus simple).
    Comme le dit Loufoque, le mapping ne charge les données que quand il en a besoin.

    Un readfile, tu alloues la mémoire pour le buffer (et si tu ne fais pas attention, tu l'initialises aussi, yesss!), ensuite tu lis physiquement les données, en passant par un buffer intermédiaire (sachant qu'au niveau le plus bas, l'accès se fait par un processus similaire au Mapping). En gros, tu as au moins deux niveaux de buffers intermédiaires (parfaitement inutiles)... Et tu lis tout d'un coup, au début, même si tu n'as besoin que d'un petit bout de la donnée...

    Quant aux appels, tu les planques dans une classe utilitaire, et c'est réglé... Tu n'écriras ce code qu'une fois. Après map.Open() map.Close(), ou ReadFile(), ca se vaut...

    Citation Envoyé par camboui Voir le message
    S'il faut commencer à gérer des pages d'accès soi-même je ne vois pas en quoi c'est désespérément simple (quid des fichiers dont la taille est >4Gb ?).
    A partir d'une certaine taille (quelques centaines de Mo), il faudra travailler sur des morceaux de fichier, quelle que soit la méthode que tu utilises... Le FileMapping ira juste plus vite sur chaque morceau.

    Cependant, comme le mapping gère la pagination, le problème se posera pour de plus gros fichiers seulement... 50 Mo, en lecture classique c'est déjà très gros. Pour un Mapping, il faut commencer à se poser la question de la pagination au delà de 400 (sur les machines que j'utilise en tous cas).

    C'est désespérément simple, parce qu'une fois le fichier mappé, tu n'as plus à gérer le buffer, ni la position du pointeur de lecture ou d'écriture, le système fait ca à ta place.

    en lecture tu as un truc comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    char *ptr=(char*) Map.data;
    // avancer de n octets
    ptr+=n;
    // lire un caractère
    char c=*ptr;
    ptr++;
    // lire un entier
    int i=*((int*)ptr);
    // reculer de 10 octets
    ptr-=10;
    Si tu as déjà fait des pointeurs, tu sais faire du mapping... Et bien sur, pour des fichiers plus sructurés, rien ne t'empêche, au lieu d'utiliser des pointeurs sur type natif (int et char) de travailler sur des types utilisateurs. Tu as alors une sérialisation très rapide. Et le pointeur est... un pointeur... tu peux le passer à des fonctions normales, qui par exemple feraient des calculs sur des tableaux.

    Observe que tu n'as pas de buffer: le buffer, c'est le fichier... C'est vraiment simple.

    3D Archi : je pense que dans ce cas précis, il ira plus vite avec un mapping, parce qu'il n'aura plus de gestion de buffer ni de pointeur de lecture. Par ailleurs, si son fichier d'entrée se complexifie (en demandant une lecture non séquentielle), le mapping va gagner beaucoup de temps.

    Citation Envoyé par camboui Voir le message
    Mais bon, je ne connais pas, et j'ai beau lire la doc de MS, je ne comprends pas comment utiliser ça avec des fichiers (sauf pour faire du shared memory entre process où là, en effet, ça semble simple).
    Ce n'est pas grave, essaye le... Tu as les fonctions pour ouvrir et fermer le mapping, après c'est simple comme un pointeur et des casts.

    Prends un gros fichier rectangulaire (un csv avec plein de colonnes et de lignes, ou un ascii rectangulaire), genre 1000 colonnes fois 10000 lignes, et calcule la somme de quelques colonnes prises au hasard...

    Essaye avec un mapping, essaye avec des getline(), ou des read(), et mesure le temps utilisé.


    En lecture écriture, c'est encore plus violent, car tu peux tout faire 'en place'. Par exemple, tu pourrais écrire un fichier en sens inverse de la façon suivante

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    map.Open();
    char *p=map.Data;
    char *q=map.Dat+Length-1;
    while(p!=q) {
      char c=*p;
      *p=*q;
      *q=c;
      p++;
      q--;
    }
    map.Close();
    Pour réordonner des fichiers, ou des choses comme cela, le mapping, c'est très efficace...

    Francois
    Dernière modification par Invité ; 30/10/2009 à 23h04.

  12. #12
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Merci beaucoup pour la réponse exhaustive.
    Faudra que j'essaie et que je compare car j'ai souvent bufferizé mes accès fichiers.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Buffer Optimal pour gros fichiers
    Par Orkhidion dans le forum C
    Réponses: 5
    Dernier message: 01/12/2009, 16h06
  2. Upload par FTP avec des gros fichiers
    Par __fabrice dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 8
    Dernier message: 07/06/2006, 12h08
  3. plusieurs pages pour gros fichiers (processeur js)
    Par arnobidul dans le forum XML/XSL et SOAP
    Réponses: 1
    Dernier message: 25/07/2005, 09h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo