CMMotionManager - Memory Leak

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • CMMotionManager - Memory Leak

    Hallo liebes Forum,

    ich habe meine App heute mit Instruments analysiert und dabei festgestellt, dass es beim CMMotionManager wohl einen Memory Leak geben muss:

    PHP-Quellcode

    1. motionManager = [[CMMotionManager alloc] init];
    2. NSOperationQueue *queue = [NSOperationQueue currentQueue];
    3. if (motionManager.accelerometerAvailable) {
    4. motionManager.accelerometerUpdateInterval = (1./CONST_fps);
    5. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
    6. {
    7. // Hier wird eine Menge Speicher allokiert, aber nicht wieder freigegeben!
    8. }];
    Alles anzeigen


    Definiert habe ich die Klasse als SingleInstance.


    Wenn ich folgendes auskommentiere, wird kein Speicher mehr allokiert:

    PHP-Quellcode

    1. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
    2. {
    3. // Dieser Block wird auskommentiert:
    4. }];


    PHP-Quellcode

    1. motionManager = [[CMMotionManager alloc] init];
    2. NSOperationQueue *queue = [NSOperationQueue currentQueue];
    3. if (motionManager.accelerometerAvailable) {
    4. motionManager.accelerometerUpdateInterval = (1./CONST_fps);
    5. /*
    6. * Dieser Block wird auskommentiert, kein Speicher wird mehr allokiert
    7. [motionManager startAccelerometerUpdatesToQueue:queue
    8. withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
    9. {
    10. // Hier wird eine Menge Speicher allokiert, aber nicht wieder freigegeben!
    11. }];
    12. */
    Alles anzeigen


    Ich verstehe einfach nicht wie ich den Speicher wieder freigeben kann der hier über CMAccelerometerData *accelerometerData allokiert wird. Immerhin sind das einige MB pro Minute.

    Was kann ich hier tun, um das in den Griff zu bekommen?


    Die Dokumentation von Apple konnte mir bisher nicht weiterhelfen und Google hat mich auch nicht schlauer gemacht.



    Ich würde mich sehr über eure Hinweise freuen. Gerne könnt ihr meinen Code auch direkt editieren. :)



    Vielen Dank im Voraus!


    OsnaTiger
  • OsnaTiger schrieb:


    Ich würde mich sehr über eure Hinweise freuen. Gerne könnt ihr meinen Code auch direkt editieren. :)


    Es passiert in dem Block

    Quellcode

    1. // Hier wird eine Menge Speicher allokiert, aber nicht wieder freigegeben!


    Kommentier den einfach aus, dann bist du das Speicherproblem los.

    Hat der Code noch weitere Funktionalität? Auf jeden Fall ist es gut den Code bei dem du das Problem vermutest nicht zu posten, das wäre viel zu einfach. Hier sind ja alles Profis, die müssen das auch so können. 8o
  • SteveJ schrieb:

    OsnaTiger schrieb:


    Ich würde mich sehr über eure Hinweise freuen. Gerne könnt ihr meinen Code auch direkt editieren. :)


    Es passiert in dem Block

    Quellcode

    1. // Hier wird eine Menge Speicher allokiert, aber nicht wieder freigegeben!


    Kommentier den einfach aus, dann bist du das Speicherproblem los.

    Hat der Code noch weitere Funktionalität? Auf jeden Fall ist es gut den Code bei dem du das Problem vermutest nicht zu posten, das wäre viel zu einfach. Hier sind ja alles Profis, die müssen das auch so können. 8o

    ? Wie darf ich deine Antwort verstehen?

    Selbst wenn in dem Block nichts enthalten ist, wird Speicher allokiert - siehe Screenshot von Instruments.

    -> (CMAccelerometerData *accelerometerData


    Es reicht schon folgendes zu implementieren:

    PHP-Quellcode

    1. motionManager = [[CMMotionManager alloc] init];
    2. NSOperationQueue *queue = [NSOperationQueue currentQueue];
    3. if (motionManager.accelerometerAvailable) {
    4. motionManager.accelerometerUpdateInterval = (1./CONST_fps);
    5. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
    6. {
    7. }];
    8. }
    Alles anzeigen


    Das ist ja der Witz. Probiere es selbst aus, dann wirst du sehen was Instruments dazu sagt.
  • Ohne das Ganze jetzt zu kennen oder gar nachgeschlagen zu haben. Die Funktion heist UpdatesToQueue. Ich gehe also mal davon aus, dass somit jede Änderung an dem CM einen neuen Eintrag in die Queue zur Folge hat. Da ist es doch total logisch, dass wenn diese Queue nicht abgearbeitet wird (und das machst du ja anscheinend nicht) diese immer voller und der Speicher damit immer weniger wird.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Thallius schrieb:

    Ohne das Ganze jetzt zu kennen oder gar nachgeschlagen zu haben. Die Funktion heist UpdatesToQueue. Ich gehe also mal davon aus, dass somit jede Änderung an dem CM einen neuen Eintrag in die Queue zur Folge hat. Da ist es doch total logisch, dass wenn diese Queue nicht abgearbeitet wird (und das machst du ja anscheinend nicht) diese immer voller und der Speicher damit immer weniger wird.

    Gruß

    Claus
    Hallo Claus,

    vielen Dank für deinen Beitrag. Das was du schreibst hört sich völlig logisch an, allerdings ist mir nicht so ganz klar wie man dann diese Einträge löschen könnte, wenn eine Iteration des Blocks abgearbeitet wurde.

    Ich versuche den NSOperationQueue Speicher wieder freizugeben, aber es werden immer noch aller ca. 10 Sekunden gut 500 KB Speicher allokiert. Wenn der Code so wie ich Ihn nachfolgende abgebildet habe direkt in Xcode ausgeführt wird, zeigt Instruments diesen Speicherverbrauch ja 10 Sekunden an.

    PHP-Quellcode

    1. __block NSOperationQueue *queue = [NSOperationQueue currentQueue];
    2. if (motionManager.accelerometerAvailable) {
    3. motionManager.accelerometerUpdateInterval = (1./CONST_fps);
    4. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:
    5. ^(CMAccelerometerData *accelerometerData, NSError *error) {
    6. if ([queue retainCount] > 1)
    7. {
    8. [queue release];
    9. }
    10. queue = nil;
    11. accelerometerData = nil;
    12. }];
    13. }
    Alles anzeigen


    Es gibt auch keine Operation wie man diese Queue ansonsten leeren kann. Mich macht die Tatsache, dass aller 10 Sekunden 500 KB Speicher gefressen werden total verrückt. :(


    Wie kann man das vermeiden?
  • was man NIE machen darf ist
    if ([queue retainCount] > 1)

    Und queue musst Du ja auch nicht freigeben, das ist die currentQueue. Vmtl. liegt aber kein Memory Leak vor, sondern einfach nur der Finger wo drauf, so das was nicht freigegeben wird. Ich kenne den Manager nicht, so dass ich leider nicht weiterhelfen kann.

    volker
  • Also auf den ersten Blick sieht das alles ganz richtig aus.

    Lass Dir mal bei jedem Durchlauf [queue operationCount] ausgeben. Vermutlich wird mit jedem Durchlauf ein weiteres Objekt in deine Queue gestopft.
    Bei 1/CONST_fps (was ist das eigentlich? 1/30? Also alle 0.03 Sekunden?) hast Du dann alle 10 Sekunden beispielsweise 333 Durchläufe hinter dir. Wenn jeder Durchlauf nur 1kB an Daten braucht, kommst Du locker auf 33kB je Sekunde.
    Die müssen auch aus der Queue raus. Mal eben freigeben und auf nil setzen ist keine gute Idee.

    Vermutlich brauchen Deine Operations einfach eine Ewigkeit, bis sie fertig sind. Dementsprechend wächst die Kette stetig an, bis sich Neuerstellung und Ausführung die Waage halten.

    Eventuell ist der Ansatz mit updateToQueue auch eine schlechte Idee.
    MarbleMaze scheint es ganz anders zu machen.

    Das Apple Sample Code Stück zu MotionGraphs gibt auch nix weiter frei sondern reicht die Werte einfach weiter. Eventuell vergleichst Du bei dem Projekt einmal, wie viel Speicher es frisst.

    Noch etwas: von einem Leak sehe ich nichts. Wenn Du nach dem Stoppen der Updates die 500kB nicht los wirst, dann hast du einen Leak. So kann/wird es nur der reine Arbeitsspeicher sein – also Speicher, der zum Arbeiten genutzt wird.
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • @ Marco,

    Dank für deine Antwort.

    Das Apple Sample Code Stück zu MotionGraphs
    gibt auch nix weiter frei sondern reicht die Werte einfach weiter.
    Eventuell vergleichst Du bei dem Projekt einmal, wie viel Speicher es
    frisst.
    Ich habe mit das Apple Sample Code Stück mal angeschaut. Auch hier wird eine Menge Speicher gefressen. Instruments liefert hier ein ähnliches Muster wie bei mir. Auch sind einige RefCt auf 1.

    - Update aller 1/25
    - [queue operationCount]: 1



    Wenn ich die Updates stoppe, dann bleibe ich auch auf den Speicher sitzen - es wird absolut nichts wieder freigegeben:

    PHP-Quellcode

    1. [motionManager stopDeviceMotionUpdates];
    2. [motionManager release];
    3. motionManager = nil;


    Noch etwas: von einem Leak sehe ich nichts. Wenn Du nach dem Stoppen der
    Updates die 500kB nicht los wirst, dann hast du einen Leak. So
    kann/wird es nur der reine Arbeitsspeicher sein – also Speicher, der zum
    Arbeiten genutzt wird.
    Das ist ja gerade das Problem. Meine App braucht grundsätzlich überdurchschnittlich viel RAM (ca. 80 MB) im Betrieb. Das liegt am Framework und an der Art und Weise wie die App dynamisch aufgebaut wird.
    Wenn nun noch aller 10 Sekunden 500 KB verbraucht werden ist nach einigen Stunden der RAM voll und die App wird folglich beendet. Allerdings ist die App für den Dauerbetrieb vorgesehen. Doof?! 8| :S


    MarbleMaze werde ich mir mal etwas ganeuer anschauen. es ist mir allerdings unbegreiflich, wieso selbst das Apple Sample so viel Speicher verbraucht ohne ihn wieder freizugeben. :huh:
  • Wenn ich die [queue operations] ausgebe, dann finde ich auch meine NSBlockOperation wieder:

    PHP-Quellcode

    1. 2013-12-03 11:30:05.240 App[593:60b] [queue operations]: (
    2. "<NSBlockOperation: 0x15642140>"
    3. )


    PHP-Quellcode

    1. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:
    2. ^(CMAccelerometerData *accelerometerData, NSError *error) {
    3. NSLog(@"[queue operations]: %@", [queue operations]);
    4. ...



    Nachdem der Block abgearbeitet wurde ist die NSBlockOperation finished. Vorher kann ich die Operation auch nicht via release freigeben, da es dann zu folgenden Fehler kommt:

    PHP-Quellcode

    1. *** -[NSBlockOperation isFinished]: message sent to deallocated instance 0x176a2210


    Das ist auch genau der CtRe der in Instruments auf 1 bleibt und nie auf 0 zurückgesetzt wird. Somit wird hier fleißig Speicher im RAM belegt und nie wieder freigegeben.


    Was kann man denn nun hier noch machen? Eigentlich könnte man hier schon von nem Bug reden oder? Das ist definitiv ein memory Leak, da ich nach dem Stoppen der Updates auch keinen Speicher mehr freigegeben bekomme.
  • OsnaTiger schrieb:

    es ist mir allerdings unbegreiflich, wieso selbst das Apple Sample so viel Speicher verbraucht ohne ihn wieder freizugeben. :huh:

    MotionGraph nutzt ARC, Du offenbar nicht.
    Apple haben in ihren Samples seit je her nicht allzu sehr auf das Memory Management geachtet.

    Nachdem Du die Updates des MotionManagers gestoppt hast ist der richtige Zeitpunkt gekommen, die Queue mittels [queue release]; queue = nil; freizugeben.
    (Noch richtiger wäre es gewesen, Du hättest sie bei der Erzeugung direkt mit einem -autorelease versehen.)

    Du sagst, Du bleibst nach dem Stoppen der Updates auf Deinem Speicher sitzen.
    Heißt das, dass die Queue nach Beenden der Updates nicht weiter anwächst?
    Das bestätigte meinen Verdacht, dass die Objekte nicht zügig genug abgearbeitet werden.

    Und die Reference Counts sind nach wie vor völlig egal. Finger weg davon. Die gehen Dich nichts an.
    Die Operation Counts sind spannend. Vielleicht ist auch jede einzelne NSOperation 500kB groß?

    Zum zweiten Beitrag:
    Der Fehler sagt Dir, dass bereits zum Zeitpunkt von [operation release] das Objekt dealloziert wurde, also faktisch nicht mehr existiert.

    Noch einmal, es ist KEIN Memory Leak, dass Du nach Beenden der Updates die Queue NICHT aufräumst. ;)
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • OsnaTiger schrieb:

    Wenn ich die [queue operations] ausgebe, dann finde ich auch meine NSBlockOperation wieder:

    PHP-Quellcode

    1. 2013-12-03 11:30:05.240 App[593:60b] [queue operations]: (
    2. "<NSBlockOperation: 0x15642140>"
    3. )


    PHP-Quellcode

    1. [motionManager startAccelerometerUpdatesToQueue:queue withHandler:
    2. ^(CMAccelerometerData *accelerometerData, NSError *error) {
    3. NSLog(@"[queue operations]: %@", [queue operations]);
    4. ...



    Nachdem der Block abgearbeitet wurde ist die NSBlockOperation finished. Vorher kann ich die Operation auch nicht via release freigeben, da es dann zu folgenden Fehler kommt:

    PHP-Quellcode

    1. *** -[NSBlockOperation isFinished]: message sent to deallocated instance 0x176a2210


    Das ist auch genau der CtRe der in Instruments auf 1 bleibt und nie auf 0 zurückgesetzt wird. Somit wird hier fleißig Speicher im RAM belegt und nie wieder freigegeben.


    Was kann man denn nun hier noch machen? Eigentlich könnte man hier schon von nem Bug reden oder? Das ist definitiv ein memory Leak, da ich nach dem Stoppen der Updates auch keinen Speicher mehr freigegeben bekomme.


    Du solltest erstmal herausfinden Was genau da allokiert wird. Instruments sagt dir das doch genau.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Natürlich wird immer noch Speicher belegt. Wie sollte es auch sonst gehen?

    Du schaust Dir permanent die lebenden Objekte an und wunderst Dich, dass diese Speicher belegen. (14.800 lebende Objekte. Fast 32.000 Objekte insgesamt. Und nur 925kB aktiv in Benutzung. Da kann man eigentlich nicht meckern.)
    Dabei hast Du unterhalb der Allocations-Übersicht noch eine Leak-Übersicht.
    Auf dieser siehst Du, welche Objekte definitiv ein Speicherleck verursachen, also niemals mehr zugreifbar sind.
    Und die sind gaaaaanz am Anfang. Ein paar Kilobyte wie es aussieht.

    Wenn Du wissen willst wie schnell Dein Auto aktuell fährt, schaust Du nicht auf den Drehzahlmesser und triffst Annahmen in Verbindung mit der Straßenneigung und dem eingelegten Gang. Du schaust auf Dein Tachometer.
    Warum schaust Du also auf die Allocations, wenn Du wissen willst, wie groß Deine Speicherlecks sind?
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Wenn ich in Xcode auf den Debug Navigator klicke und dann auf Memory dann wird ja der Speicher in MB angezeigt, die meine APP momentan belegt.

    Der Speicherverbrauch wächst mit der Zeit. Um wieviel zeigt mein letzter Screenshot (ein paar 100 KB pro Minute). Das macht mich ein wenig nervös.


    Ich meine, wenn unter Memory Utilized ein steigender Speicherverbrauch zu sehen ist, dann wird doch irgendwo immer mehr Speicher belegt aber nicht wieder freigegeben, oder?

    Wenn ich den CAMotionManager komplett auskommentiere dann wächst der Speicherbedarf nicht mehr, obwohl der CAMotionManager ja eigentlich nichts macht, wenn der nicht auskommentiert wird. Zudem habe ich ARC aktiviert.


    Wo ist hier mein Dankfehler?

    Die lebenden Objekte werden mit der Zeit immer mehr, so lange der CAMotionManager läuft.



    Vllt. sind nicht die Speicher Leaks mein Problem sondern der steigende Speicherbedarf.

    PHP-Quellcode

    1. if (motionManager.accelerometerAvailable) {
    2. motionManager.accelerometerUpdateInterval = (1./CONST_fps);
    3. [motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:
    4. ^(CMAccelerometerData *accelerometerData, NSError *error) {
    5. // Der Block ist dabei komplett leer!
    6. // Hier steht kein Code!
    7. }];
    Alles anzeigen



    In ca. 2 Minuten wächst der Speicherverbrauch um ca 2 MB an - laut Memory Utilized. D.h., nach 8 Stunden würde iOS die App spätestens beenden, weil die App dann um die 600 MB RAM belegen würde. Eigentlich wird es ja schin kritisch, wenn über 45 % des RAM belegt werden. Der kritische Bereich würde nach ca. 4 bis 5 Stunden erreicht.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von OsnaTiger ()

  • OsnaTiger schrieb:

    Wo ist hier mein Denkfehler?

    Ich weiß nicht, was genau Du vor hast.

    Wenn es sich um eine Art Spiel handelt, worauf CONST_fps hinweist (wer spielt denn bitte 4-6 Stunden?), solltest Du wirklich einmal den Ansatz vom MarbleMaze ansehen.
    In dem Fall könnte der Denkfehler nämlich der sein, dass Du alles in die Queue pumpst.
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Ok, sorry erst mal - mir war nicht klar das der Block wirklich leer war.

    Du packst Blöcke in schneller Reihenfolge auf [NSOperationQueue currentQueue] - das ist wahrscheinlich in deinem Fall die Main Queue. Apple meint dazu: "Because the processed events might arrive at a high rate, using the main operation queue is not recommended."

    Generell ist die Frage was du mit den Events willst wenn du sie nicht verarbeitest. Nach ein paar Stunden ist die Batterie alle - wahrscheinlich bevor der Speicher voll ist.
  • Marco Feltmann schrieb:

    OsnaTiger schrieb:

    Wo ist hier mein Denkfehler?

    Ich weiß nicht, was genau Du vor hast.

    Wenn es sich um eine Art Spiel handelt, worauf CONST_fps hinweist (wer spielt denn bitte 4-6 Stunden?), solltest Du wirklich einmal den Ansatz vom MarbleMaze ansehen.
    In dem Fall könnte der Denkfehler nämlich der sein, dass Du alles in die Queue pumpst.
    Vielen Dank für deine Hilfestellungen. Ich werde mir den Ansatz vom MarbleMaze mal ansehen und die Ergebnisse dann posten.

    Wahrscheinlich liegt das ja tatsächlich an der Queue.


    Bei der App handelt es sich nicht unbedingt um ein Spiel. Die App könnte allerdings auf einer Ladestation im Dauerbetrieb sein. Über die Lage des Smartphones werden bestimmte Aktionen ausgelöst. Das beeinflusst auch in gewissen Weise das Verhalten der App, u.a. werden bei mir die Schatten dynamisch ausgerichtet, die einige Objekte werfen.


    Ich melde mich später mit nem Ergebnis zurück ;)
  • [SteveJ]
    Laut Dokumentation muss [NSOperationQueue currentQueue]; aber nicht die Main Queue zurückgeben.
    Es lässt sich sicherlich ein Vergleich anstellen, ob queue == [NSOperationQueue currentQueue].
    Aber falls ja: wo bekommt man in dem Fall eine andere Queue her?
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Marco Feltmann schrieb:


    Laut Dokumentation muss [NSOperationQueue currentQueue]; aber nicht die Main Queue zurückgeben.


    Deshalb 'wahrscheinlich'. [NSOperationQueue currentQueue] ist meistens eine blöde Idee.

    Marco Feltmann schrieb:


    Aber falls ja: wo bekommt man in dem Fall eine andere Queue her?


    [NSOperationQueue mainQueue] wenn du was mit der UI vor hast, sonst natürlich wie immer mit

    Quellcode

    1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    2. queue.name = @"My Queue";