[Résolu] Consommation de mémoire déraisonnable

berfisberfis Membre
janvier 2017 modifié dans API AppKit #1

Bonjour,


 


Instruments me montre une consommation énorme de mémoire durant l'exécution de cette méthode:



- (void) resize: (NSArray*)arr
{
// NSPredicate *predicate = [NSPredicate predicateWithBlock: ^BOOL (id evaluatedObject, NSDictionary *bindings){
NSDictionary *fileDic =[self.fm attributesOfItemAtPath:[evaluatedObject path] error: nil];
unsigned long long testSize = [[fileDic valueForKey:@NSFileSize]longLongValue];
return testSize > (unsigned long long)UDC_INT(@maxFileSize)*1000;
}];
//
long totalFiles = 0;
long treatedFiles = 0;
float percentage =UDC_FLOAT(@percentage)/100.0;
NSUInteger interpolation = UDC_INT(@interpolation);
for (NSString *element in arr) {
NSArray *files = [[self.fm enumeratorAtURL:[NSURL fileURLWithPath:element]
includingPropertiesForKeys:@[;NSURLFileSizeKey]
options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:nil]allObjects];
totalFiles = totalFiles+files.count;
files = [files filteredArrayUsingPredicate:predicate];

self.indicatorMaxValue = files.count;
self.indicatorCurrentValue = 0;
[self.rest setDoubleValue:files.count];

for(NSURL *aFile in files){

self.indicatorCurrentValue ++;
[self setInfoLine:[aFile path]];
[self.rest setDoubleValue:self.indicator.maxValue-self.indicator.doubleValue];


NSImage* source = [[NSImage alloc]initByReferencingURL:aFile];
NSImageRep *rep = [source representations][0];
NSSize size = NSMakeSize ([rep pixelsWide], [rep pixelsHigh]);
[source setSize: size];
float newWidth = source.size.width;
float newHeight = source.size.height;
NSData *data;
self.numPasses = 0;
do {
newWidth = newWidth * percentage;
newHeight = newHeight * percentage;
NSImage* small = [[NSImage alloc] initWithSize:NSMakeSize(newWidth, newHeight)];
[small lockFocus];
[[NSGraphicsContext currentContext] setImageInterpolation:interpolation];
[source setSize:NSMakeSize(newWidth, newHeight)];
[source drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[small unlockFocus];
data = [[NSBitmapImageRep imageRepWithData:[small TIFFRepresentation]] representationUsingType:NSJPEGFileType properties:@{}];
self.numPasses ++;
small = nil;
} while(data.length > (long)UDC_INT(@maxFileSize)*1000);

[data writeToURL:aFile atomically:NO];
source = nil;
data = nil;
treatedFiles ++;
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]; // Forces UI to update
}
}
}

A la fin de l'exécution, toute la mémoire est libérée d'un coup. J'ai essayé de mettre les différents objets à  NIL dans l'espoir que ARC m'en débarrasse, mais il ne le fait pas.


 


Le problème, c'est qu'au bout d'un moment l'application se fige, quand le système ne me dit pas que toute la mémoire est utilisée. Comment forcer la mémoire à  se purger sour ARC, disons lorsque le fichier est écrit sur le disque, lors de [data writeToURL:aFile atomically:NO] ?


 


D'avance merci !


Mots clés:

Réponses

  • tabliertablier Membre
    juillet 2016 modifié #2

    Se passer d'Arc si c'est possible ! Arc est fait pour que le développeur ne se soucie plus de la gestion de la mémoire. Mais Arc est un automatisme, et comme tous les automatismes il est limité par sa programmation ! 


    Je n'ai pas de solution, mais les informaticiens experts qui ne sont pas en vacances vont certainement te trouver une solution.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Même avec ARC, tu peux essayer jouer avec @autoreleasepool :



    - (void)resize:(NSArray*)arr
    {
    // NSPredicate *predicate = [NSPredicate predicateWithBlock: ^BOOL (id evaluatedObject, NSDictionary *bindings)
    {
    NSDictionary *fileDic =[[NSFileManager defaultManager] attributesOfItemAtPath:[evaluatedObject path] error: nil];

    unsigned long long testSize = [[fileDic valueForKey:@NSFileSize]longLongValue];

    return testSize > (unsigned long long)UDC_INT(@maxFileSize)*1000;
    }];
    //
    long totalFiles = 0;

    long treatedFiles = 0;

    float percentage =UDC_FLOAT(@percentage)/100.0;

    NSUInteger interpolation = UDC_INT(@interpolation);

    for (NSString *element in arr)
    {
    NSArray *files = [[[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:element]
    includingPropertiesForKeys:@[;NSURLFileSizeKey]
    options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:nil]allObjects];

    totalFiles = totalFiles+files.count;

    files = [files filteredArrayUsingPredicate:predicate];

    self.indicatorMaxValue = files.count;

    self.indicatorCurrentValue = 0;

    [self.rest setDoubleValue:files.count];

    for(NSURL *aFile in files)
    {
    self.indicatorCurrentValue ++;

    [self setInfoLine:[aFile path]];

    [self.rest setDoubleValue:self.indicator.maxValue-self.indicator.doubleValue];

    @autoreleasepool
    {
    NSImage* source = [[NSImage alloc]initByReferencingURL:aFile];

    NSImageRep *rep = [source representations][0];

    NSSize size = NSMakeSize ([rep pixelsWide], [rep pixelsHigh]);

    [source setSize: size];

    float newWidth = source.size.width;

    float newHeight = source.size.height;

    NSData *data;

    self.numPasses = 0;

    do
    {
    @autoreleasepool
    {
    newWidth = newWidth * percentage;

    newHeight = newHeight * percentage;

    NSImage* small = [[NSImage alloc] initWithSize:NSMakeSize(newWidth, newHeight)];

    [small lockFocus];

    [[NSGraphicsContext currentContext] setImageInterpolation:interpolation];

    [source setSize:NSMakeSize(newWidth, newHeight)];

    [source drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];

    [small unlockFocus];

    data = [[NSBitmapImageRep imageRepWithData:[small TIFFRepresentation]] representationUsingType:NSJPEGFileType properties:@{}];

    self.numPasses ++;

    small = nil;
    }
    } while(data.length > (long)UDC_INT(@maxFileSize)*1000);

    [data writeToURL:aFile atomically:NO];

    source = nil;

    data = nil;

    treatedFiles ++;

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]; // Forces UI to update
    }
    }
    }
    }

    Mais, surtout, si ce code se trouve dans un contrôleur, tu pourrais essayer de séparer le "code de travail" dans un autre fil, en utilisant un block pour notifier le fil principal après que le travail soit achevé.


  • Merci Joanna, cela fait des années que je me fie totalement à  ARC, et j'ignorais l'existence de @autoreleasepool.


     


    Pour ce qui est du multi-treading, je l'emploie à  d'autres choses, mais ce traitement-ci se doit d'être exécuté dans le fil principal, et qu'importe si l'application ne réagit plus durant le traitement. Mais dans ce cas-ci, "ne plus réagir" signifiait "ne plus agir du tout" (hang).


     


    Grâce à  toi, l'utilisation de la mémoire passe de 310% (!) à  0.71%... et l'application parvient à  la fin de sa boucle.


  • AliGatorAliGator Membre, Modérateur
    juillet 2016 modifié #5
    ARC libère la mémoire des objets inutilisés... à  la fin de la Runloop courante.

    Or, là , tu as une double-boucle for + un do...while (*), ce qui fait que la mémoire n'est pas libérée avant que toutes ces boucles ne soient totalement finies et que tu sois sorti de ton "for" le plus externe et enfin de ta fonction et ait fini la Runloop.
    Du coup ça fait long à  attendre pour que la mémoire soit libérée, et en attendant pendant l'exécution de ces boucles imbriquées, la mémoire s'accumule en attendant de n'être libérée que plus (trop) tard.

    L'utilisation de @autoreleasepool comme te l'a conseillé Joanna est effectivement la solution, ça permet à  ARC de définir un scope dans lequel tu dis "à  la fin de ce scope de @autoreleasepool, fait une passe pour libérer tous les objets qui ont été créés dans ce scope et qui ne servent plus". Ca évite d'attendre la fin de la Runloop, tous les objets intermédiaires qui sont créés dans ce bloc @autoreleasepool { ... } sont ajoutés à  l'auto-release pool locale qui est détruite à  la fin du scope " plutôt que d'être ajoutée à  l'auto-release pool globale gérée par la RunLoop " du coup ça permet de les libérer plus vite.

    On n'a pas souvent l'occasion d'utiliser ces "@autoreleasepool" manuellement comme ici, mais quand tu fais des grosses boucles "for" qui sont longues et allouent potentiellement beaucoup de mémoire temporaire (manipulation d'images ou d'objets qui prennent de la place en mémoire par exemple), c'est justement le cas d'école où il est fortement conseillé de les utiliser. Ca veut pas dire qu'il faut en mettre à  l'intérieur de chaque boucle for que tu fais partout dans ton programme, car souvent ces boucles for ne sont pas aussi grosses ni gourmandes, mais par contre dès que tu as des grosses boucles dont le code à  l'intérieur alloue beaucoup de mémoire, c'est vraiment fait pour.


    (*) au passage, cette utilisation de double boucle "for" + un "do" fait une complexité assez élevé comme algo, j'ai pas lu l'algo en question en détail, mais il faut voir si tu peux pas paralléliser des boucles et optimiser tout ça. Même si tu veux que ton algo soit synchrone car comme tu le dis tu ne veux pas que l'application exécute autre chose en parallèle et que c'est pas grave si l'application ne réagit plus, cela veut quand même dire qu'elle prend tous les cycles CPU et que l'expérience utilisateur n'est pas optimale. Parallélisme les boucles for, même si à  la fin tu re-synchronises tout pour attendre que toutes les boucles exécutées en parallèle soient terminées avant de redonner la main, ça te permettrait de garder l'aspect synchrone au niveau fonctionnel mais de profiter quand même du parallélisme et du multi-threading du Mac vu que de nos jours ils ont plusieurs processeurs et/ou plusieurs coeurs et permettent justement ce genre de choses.

    Rien que d'utiliser "enumerateObjectsWithOptions:" (enfin un truc comme ça je me rappelle plus du nom exact de la méthode de NSArray) au lieu d'utiliser "for", et en lui passant l'option qui permet de spécifier une exécution concurrente de chaque itération, tu devrais y gagner en permettant de profiter du multi-coeur de ton processeur.
Connectez-vous ou Inscrivez-vous pour répondre.