Gestion de fichier lourd (plusieurs Go)

aranaudaranaud Membre
12:49 modifié dans API AppKit #1
Je voudrai savoir comment manipuler une partie d'un fichier sans charger l'ensemble du fichier en mémoire. La classe NSData ne semble pas pouvoir le faire.
«1

Réponses

  • fouffouf Membre
    12:49 modifié #2
    Je ne crois pas qu'il y ait de classes toutes faites pour Cocoa, par contre, je crois que ca doit être faisable en utilisant du C et des pointeurs char* ou FILE*. Il s'agit juste d'un piste de recherche, je n'ai jamais vraiment essayer ;)
  • aranaudaranaud Membre
    12:49 modifié #3
    Peut devrais-je utiliser les classes NSInputStream et NSOutputStream qui semble plus adapter pour extraire une petite partie des données dans un fichier.
    Je vais regarder pour le pointeur FILE*.
  • BruBru Membre
    12:49 modifié #4
    La classe NSFileHandle est ce qu'il te faut.

    Tu ouvres ton fichier par fileHandleForReadingAtPath:, ensuite tu positionnes le pointeur (de fichier) en utilisant seekToFileOffset:, et enfin, en utilisant readDataOfLength:, tu lis un bloc de données de la taille voulue.

    .
  • aranaudaranaud Membre
    12:49 modifié #5
    Merci Bru. o:)
  • aranaudaranaud Membre
    12:49 modifié #6
    <br />unsigned long firstBuffer;<br />readHandle = [NSFileHandle fileHandleForReadingAtPath:[cheminFichierFragmenter stringValue]];<br />[readHandle seekToFileOffset:firstBuffer];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  // mise en place du pointeur<br />int i = 50;<br />[readHandle readDataOfLength:i];<br />NSLog(@&quot;data %@&quot;, [NSData dataWithBytes:(char *)firstBuffer length:200]);<br />
    

    Nota : si je definie unsigned long long firstBuffer; , sa plante à  la ligne de la mise en place du pointeur.

    J'ai dù louper quelque chose. Quelque soit la valeur de i, j'ai toujours la même chose dans les datas.
  • 12:49 modifié #7
    il vaut combien firstBuffer ?
  • BruBru Membre
    janvier 2006 modifié #8
    dans 1136288191:

    Nota : si je definie unsigned long long firstBuffer; , sa plante à  la ligne de la mise en place du pointeur.
    J'ai dù louper quelque chose. Quelque soit la valeur de i, j'ai toujours la même chose dans les datas.


    Ta variable firstBuffer n'est pas initialisée la première fois que tu l'utilises. Donc, elle peut contenir n'importe quelle valeur, et surtout, une valeur qui fait que ça pointe en dehors du fichier.

    Si tu dois commencer la lecture au début du fichier, alors juste apès le fileHandleForReadingAtPath:, ajoute cette ligne :
    <br />firstBuffer=[readHandle offsetInFile];<br />
    


    Ensuite, tu dois "bouger" le pointeur vers un autre endroit du fichier pour lire le bloc suivant.
    Dans ton exemple, pour lire un second bloc de 50 octets à  la suite du premier, il faut incrémenter le pointeur de 50 avec d'effectuer la seconde lecture :
    <br />[readHandle seekToFileOffset:[readHandle offsetInFile]+50];<br />
    


    ou (pour réutiliser ta variable firstBuffer) :

    <br />firstBuffer+=50;<br />[readHandle seekToFileOffset:firstBuffer];<br />
    


    .
  • aranaudaranaud Membre
    12:49 modifié #9
    Merci beaucoup. Sa marche. <3 <3
  • aranaudaranaud Membre
    12:49 modifié #10
    C'est mort pour les fichiers de plus de 2 Go.
    <br />archivageDataFragment(3196,0xa000ed68) malloc: *** vm_allocate(size=200003584) failed (error code=3)<br />archivageDataFragment(3196,0xa000ed68) malloc: *** error: can&#39;t allocate region<br />archivageDataFragment(3196,0xa000ed68) malloc: *** set a breakpoint in szone_error to debug<br />2006-01-03 16:30:04.123 archivageDataFragment[3196] *** -[NSConcreteFileHandle readDataOfLength:]: Bad address<br />
    
  • BruBru Membre
    12:49 modifié #11
    Ton pointeur doit être un unsigned long long.
    Vérifie et retest.

    D'autre part, la gestion des gros fichiers n'est possible qu'en partition HFS+, vérifie le aussi.

    Je ferais un test ce soir de mon côté.

    .
  • BruBru Membre
    12:49 modifié #12
    Bon, chez moi (10.4) aucun problème pour lire les 3000 derniers octets d'un fichier de 3,47 go.

    C'est ton code qui doit être bancal, d'autant plus qu'il me semble que tu n'as pas tout compris sur la lecture partielle d'un fichier.

    Fais nous voir le code en question...


    .
  • aranaudaranaud Membre
    janvier 2006 modifié #13
    J'ai une énorme fuite de mémoire. :o
    Je crois que le problème se trouve là . Au bout d'un moment, sa plante le programme.

    Edit : j'ai rajoutté les fichiers sources

    [Fichier joint supprimé par l'administrateur]
  • BruBru Membre
    12:49 modifié #14
    dans 1136321981:

    J'ai une énorme fuite de mémoire. :o
    Je crois que le problème se trouve là . Au bout d'un moment, sa plante le programme.


    Il n'y a pas réellement de fuite mémoire, mais une utilisation catastrophique de la mémoire.

    Tu as créé une boucle imbriquée qui permet de lire des fragments de fichiers pour les écrire individuellement dans des fichiers. Or dans ces boucles, tu manipules beaucoup d'objets qui sont en auto-release.

    Or ces objets en auto-release ne sont purgés qu'en fin de boucle d'événement, donc, dans ton programme, ils ne seront purgés de la mémoire qu'après avoir quitté la méthode fragment:.

    En conclusion, au fur et à  mesure de l'avancement de tes boucles imbriquées, tu emplis la mémoire de ton appli jusqu'à  saturation.

    La première chose à  faire est de créer un autoreleasePool en début de méthode copieFichier:sortir:buffer:avance:, et de le purger à  la sortie de cette méthode.

    En second, il faut faire de même (création d'un autoreleasePool) en début de boucle while dans la méthode [/b]fragment:[/b], et purger ce pool en fin de boucle.

    Enfin, essaie de simplifier ton code... Tu créés beaucoup d'objets redondants, ce qui nuit, et aux performances de ton appli, et à  la l'intégrité de la mémoire (fragmentation de la mémoire).
    L'exemple le plus frappant est la partie où tu créés le nom de fichier fragment...

    .
  • Eddy58Eddy58 Membre
    janvier 2006 modifié #15
    Il est aussi possible de se passer des méthodes constructeurs et des autorelease pools en privilégiant l'utilisation des méthodes init... et release, afin d'avoir un maximum de contrôle sur la gestion mémoire des différents objets. :)
  • 12:49 modifié #16
    Eddy88>non, dans le cas ou tu ne fais que des alloc/release la mémoire n'est vraiment libérée qu'en fin à  l'autorelease pool de la méthode. Avec un autorelease pool releasé régulièrement (genre dans chaque "passage" d'un for(...)) la mémoire est réellement libérée à  chaque fois que tu le décides...
  • aranaudaranaud Membre
    janvier 2006 modifié #17
    dans 1136326269:

    Il n'y a pas réellement de fuite mémoire, mais une utilisation catastrophique de la mémoire.
    ....

    Merci à  tous de votre aide.
    Je vais regarder pour la mémoire avec autorelease.

    Edit : sa marche  :p :adios!:
  • BruBru Membre
    12:49 modifié #18
    dans 1136332612:

    Il est aussi possible de se passer des méthodes constructeurs et des autorelease pools en privilégiant l'utilisation des méthodes init... et release, afin d'avoir un maximum de contrôle sur la gestion mémoire des différents objets. :)


    Sauf que parfois (pour ne pas dire souvent), tu ne peux pas faire autrement que d'avoir un objet en autorelease (et ce cas est très courant)...

    .
  • BruBru Membre
    12:49 modifié #19
    dans 1136336234:

    Eddy88>non, dans le cas ou tu ne fais que des alloc/release la mémoire n'est vraiment libérée qu'en fin à  l'autorelease pool de la méthode. Avec un autorelease pool releasé régulièrement (genre dans chaque "passage" d'un for(...)) la mémoire est réellement libérée à  chaque fois que tu le décides...


    alloc/release n'a rien avoir avec l'autoreleasePool... Release libère l'objet si son compteur de référence tombe à  0, et ceci, autoreleasePool ou pas.

    .
  • AliGatorAliGator Membre, Modérateur
    12:49 modifié #20
    Ben oui j'étais justement en train de me demander pourquoi Bru préconisait la création d'un autoreleasepool plutôt que pas d'utilisation de la fonctionnalité d'autorelease du tout  ??? 8)
  • 12:49 modifié #21
    Je ne suis pas assez descriptif.

    Entre ça :
    <br />	NSImage *img=[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@&quot;globe&quot;]];<br />	int i;<br />	for (i=0;i&lt;20;i++)<br />	{<br />		NSImage *img2=[[NSImage alloc] initWithData:[img TIFFRepresentation]];<br />		[img2 release];<br />	}<br />	return;<br />
    

    et ça :

    <br />	NSImage *img=[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@&quot;globe&quot;]];<br />	int i;<br />	for (i=0;i&lt;20;i++)<br />	{<br />		NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];<br /><br />		NSImage *img2=[[NSImage alloc] initWithData:[img TIFFRepresentation]];<br />		[img2 release];<br /><br />		[pool release];<br />	}<br />
    

    Je vois une grosse différence de consommation mémoire. Avec une grande image le premier prend plus de 400 Mo (puis les libères) alors que le second moins de 30Mo.

    Je voulais le signaler dans le cas ou "votre" fichier de 2GB sera parcouru de A à  Z...

    Je veux bien dire que le alloc/release n'a rien à  voir avec l'autoreleasepool mais l'allocation mémoire du sytème doit surement réargir avec (au moins un peu...).
  • BruBru Membre
    12:49 modifié #22
    dans 1136367260:

    Je vois une grosse différence de consommation mémoire. Avec une grande image le premier prend plus de 400 Mo (puis les libères) alors que le second moins de 30Mo.
    Je voulais le signaler dans le cas ou "votre" fichier de 2GB sera parcouru de A à  Z...
    Je veux bien dire que le alloc/release n'a rien à  voir avec l'autoreleasepool mais l'allocation mémoire du sytème doit surement réargir avec (au moins un peu...).


    Je suis ok avec toi.

    Mais l'exemple que tu montres est symptomatique de ce qu'Aranaud a fait...
    En l'occurence, le code ci-dessus s'en sort aussi bien que ta seconde version, sans allouer d'autoreleasePool :
    <br />	NSImage *img=[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@&quot;globe&quot;]];<br />	int i;<br />	NSData *dat;<br />	dat=[img TIFFRepresentation];<br />	for (i=0;i&lt;20;i++)<br />	{<br />		NSImage *img2=[[NSImage alloc] initWithData:dat];<br />		[img2 release];<br />	}<br />	return;<br />
    


    C'est donc bien le design du programme qui est en cause plus que la gestion mémoire...

    .
  • BruBru Membre
    12:49 modifié #23
    dans 1136366844:

    Ben oui j'étais justement en train de me demander pourquoi Bru préconisait la création d'un autoreleasepool plutôt que pas d'utilisation de la fonctionnalité d'autorelease du tout  ??? 8)


    En fait, il est des méthodes dont tu ne peux pas te passer, et qui renvoient des objets placés dans l'autoreleasePool.

    Dans le cas d'Aranaud, la méthode "gourmande" en mémoire est readDataOfLength: de NSFileHandle. Cette méthode lit X octets d'un fichier, et renvoie un objet NSData contenant ces octets lus. L'objet NSData retourné est en autoreleasePool.

    Si readDataOfLength: est placé dans une boucle, l'appli va vite se retrouver avec plein de NSData en attente de purge, d'où saturation...

    C'est pourquoi, dans ce cas précis, je préconise la création d'un autoreleasePool afin de faire soi-même le ménage...

    .
  • janvier 2006 modifié #24
    Ce n'est qu'un exemple, certes ton code prend moins de mémoire puis le TIFFRep n'est demandé qu'une fois... mais la mémoire allouée à  img2 reste toujours monopolisée (360Mo) jusqu'à  la fin de la boucle. Ca n'est pas le cas avec l'exemple 2 qui ne dépasasse jamais les 40 Mo (environ...).

    A la première lecture j'ai pensé que le fait de déclarer les variables en amont avait de l'importance... ?
  • aranaudaranaud Membre
    12:49 modifié #25
    dans 1136369393:

    Mais l'exemple que tu montres est symptomatique de ce qu'Aranaud a fait...

    :o non, n'en jeter plus.

    Jusqu'à  maintenant, je me suis jamais vraiment intéressé à  la gestion de la mémoire. J'avais bien lus quelques sujets mais sans un exemple concret (libérer quelques octets n'est pas très concret mais là  plusieurs Giga...).

    Juste une dernière question (c'est qu'on dit), l'écriture sur le disque dur des données se fait lorque ont fait
    [writeHandle closeFile];
    

    ou
    [writeHandle writeData:data
    
  • AliGatorAliGator Membre, Modérateur
    12:49 modifié #26
    dans 1136370074:

    En fait, il est des méthodes dont tu ne peux pas te passer, et qui renvoient des objets placés dans l'autoreleasePool.
    Oui oui j'ai bien compris :)
    En fait j'aurais répondu avant ta toute première réponse (disant de rajouter des autoreleasepool) j'aurais juste conseillé d'utiliser des objets non-autoreleasés. (d'où le "???")
    Et puis quand j'ai vu ta réponse je me suis dit "mais pourquoi il se complique la vie ?" et puis j'ai réfléchi 30s (et me suis dit que t'avais sûrement une bonne raison)... et j'ai rapidement réalisé que des fois on n'avait pas le choix  (du coup, d'où le  8))

    Voilà  la traduction de mes smileys  ;D :)

    ----

    Sinon supermic, pourquoi la mémoire alloué à  img2 dans la boucle reste toujours monopolisée ? Alors que dans la boucle on fait le alloc/init sur img2, mais aussi son release, donc la mémoire devrait être libérée sur ce img2 !
    A moins bien sûr qu'on ait introduit une fuite de mémoire genre réaffecté une valeur à  img2 avant de lui avoir envoyé le nombre suffisant de "retains" pour balancer les précédents "alloc/init" (ou "copy" ou autres), et dans ce cas img2 n'autait pas été libéré (retainCount pas tombé à  zéro, dealloc non appelé en interne).

    Mais ici on fait le alloc/init au début de la boucle, et le release à  la fin de la boucle, donc la mémoire allouée à  img2 devrait être libérée en fin de boucle et la place mémoire utilisée par ta boucle réduite à  quelques Mo, non ?
    Ta conclusion qui dit que ça prend 360Mo), c'est le résultat de tests ou une conclusion en voyant le code ?
  • aranaudaranaud Membre
    12:49 modifié #27
    dans 1136373270:

    Ta conclusion qui dit que ça prend 360Mo), c'est le résultat de tests ou une conclusion en voyant le code ?

    Tu devrais comparer le programme ci-dessous avec la version précédante http://www.objective-cocoa.org/forum/index.php?action=dlattach;topic=1489.0;attach=1018

    Fait l'essai avec un fichier de plusieurs giga

    [Fichier joint supprimé par l'administrateur]
  • AliGatorAliGator Membre, Modérateur
    12:49 modifié #28
    dans 1136374629:

    dans 1136373270:

    Ta conclusion qui dit que ça prend 360Mo), c'est le résultat de tests ou une conclusion en voyant le code ?

    Tu devrais comparer le programme ci-dessous avec la version précédante http://www.objective-cocoa.org/forum/index.php?action=dlattach;topic=1489.0;attach=1018

    Fait l'essai avec un fichier de plusieurs giga
    Je tâche de télécharger ça ce soir et de faire quelques tests pour voir, dès que j'ai mon mac entre les mains.

    Au fait, pour savoir la mémoire ça bouffe, vous regardez où ? un coup de top dans le terminal ? (ou via Moniteur d'Activité, quoi) ? Ou alors plus sioux ? (genre mallocDebug ou du genre ?)
  • aranaudaranaud Membre
    12:49 modifié #29
    dans 1136377153:

    Au fait, pour savoir la mémoire ça bouffe, vous regardez où ? un coup de top dans le terminal ? (ou via Moniteur d'Activité, quoi) ? Ou alors plus sioux ? (genre mallocDebug ou du genre ?)

    Dans le moniteur d'activité. Mais ne t'inquiète pas. La différence entre les deux versions s'entent au bruit du disque dur quand il swap. :P
  • AliGatorAliGator Membre, Modérateur
    12:49 modifié #30
    Bon j'ai regardé pendant ma pause (sur le PC, donc j'ai juste ouvert fichier gestionData.m dans un éditeur de texte, c'est pas très coloré mais j'ai esasyé de me dépatouiller avec, j'ai pas pu attendre ce soir  :)).

    Mais pour m'éviter d'avoir à  attendre ce soir de mieux cerner le code, peux-tu m'indiquer de quelle boucle while il est sujet ici ?
    - J'ai un [tt]while (j < nombrePartie)[/tt] (bourré de variables 'autorelease' donc gestion de la mémoire catastrophique si nombrePartie (tiens pas de 's' à  tes noms de variables pluriels ?  ;D) commence à  être un tant soit peu non négligeable ???
    - J'ai aussi un [tt]while (objectFichier = [enumerateurFichier nextObject])[/tt]
    - Ainsi qu'un [tt]while (im < nombreBuffer)[/tt]

    Donc si tu pouvais dégrossir en attendant que je regarde ça chez moi dans Xcode :P
  • aranaudaranaud Membre
    janvier 2006 modifié #31
    C'est l'ensemble des boucles while qui possaient problème. Si j'ai bien comprie, les variables charge des données en mémoires mais ne libère ceci que une fois la boucle while finie.

    Dans - (IBAction)fragment:(id)sender :
    nombrePartie : représente le nombre de fichier créer - 1 (le dernier est fait hors de la boucle)
    j : N° du fichier en cours

    Dans - (void)copieFichier:(NSFileHandle *)entrer sortir:(NSFileHandle *)sortir buffer:(unsigned long long)buffer avance:(unsigned long long)avance :
    J'ai choisi que le plus gros buffers (partie de données copiées) soit de 20 Mo (il semblerait que le plus gros buffer que l'on peut faire soit d'environs 256 Mo).
    nombreBuffer : reprèsente le nombre de buffers dans un fichier - 1 (la même raison que...)
    im : N° du buffer en cours

    Dans - (IBAction)reconstitue:(id)sender :
    objectFichier : réprèsente le nom du fichier partie


    Dans le nouveau, j'ai remplacé - (void)copieFichier:(NSFileHandle *)entrer sortir:(NSFileHandle *)sortir buffer:(unsigned long long)buffer avance:(unsigned long long)avance par
    <br />- (void)copieFichierB:(NSString *)nomFichierEntrant sortir:(NSString *)nomFichierSortant bufferEntrant:(unsigned long long)bufferEntrant bufferSortant:(unsigned long long)bufferSortant avance:(unsigned long long)avance<br />{<br />    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];                                 // pour la gestion de la mémoire<br />    <br />    //  init lecture fichier entrant<br />    NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:nomFichierEntrant];     // ouverture du fichier en lecture<br />    [readHandle seekToFileOffset:bufferEntrant];                                                // mise en place du pointeur en possition bufferEntrant<br />    <br />    //  init écriture fichier sortant<br />    NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:nomFichierSortant];    // ouverture du fichier en écriture<br />    [writeHandle seekToEndOfFile];                                                              // mise du pointeur à  la fin du fichier<br />    <br />    [writeHandle writeData:[readHandle readDataOfLength:avance]];                               //  copie des données de taille avance<br />    <br />    //  fermeture des fichiers<br />    [writeHandle closeFile];<br />    [readHandle closeFile];<br />    [pool release];<br />}<br />
    


    :p
    Sui pas sûr des très claire.

    En résumer, dans la première version, ça revient à  charger le fichier en mémoire alors que dans la seconde, au max 20 Mo.
Connectez-vous ou Inscrivez-vous pour répondre.