Gestion de fichier lourd (plusieurs Go)
aranaud
Membre
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.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je vais regarder pour le pointeur FILE*.
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.
.
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 :
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 :
ou (pour réutiliser ta variable firstBuffer) :
.
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é.
.
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...
.
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]
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...
.
Merci à tous de votre aide.
Je vais regarder pour la mémoire avec autorelease.
Edit : sa marche :adios!:
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)...
.
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.
.
Entre ça :
et ça :
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 :
C'est donc bien le design du programme qui est en cause plus que la gestion mémoire...
.
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...
.
A la première lecture j'ai pensé que le fait de déclarer les variables en amont avait de l'importance... ?
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
ou
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 ?
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]
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
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
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
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.