Leistung in iOS oder wie man den Hauptthread auslagert. Teil 1



Es gibt verschiedene Tricks und Tricks, die helfen, die Arbeit von iOS-Anwendungen zu optimieren, wenn eine Aufgabe in 16,67 Millisekunden erledigt sein soll. Wir erklären Ihnen, wie Sie den Hauptthread entladen und welche Tools besser zum Verfolgen des darin enthaltenen Aufrufstapels geeignet sind.


„Leute, stellen wir uns vor, Sie können die Startzeit um 10 Sekunden verkürzen. Wenn wir dies mit 5 Millionen Benutzern multiplizieren, haben wir täglich 50 Millionen Sekunden. In einem Jahr werden dies etwa zehn Menschenleben sein. Wenn Sie den ersten Download 10 Sekunden schneller durchführen, retten Sie mehrere zehn Leben. Es ist es wirklich wert, nicht wahr? "

Steve Jobs über die Leistung (Startzeit des Apple II-Computers).


Der Artikel basiert auf einem Bericht des Fyusion iOS-Entwicklers Luc Parham, der letztes Jahr auf der MBLT DEV International Mobile Developers Conference sprach.


MBLT DEV 2018 findet am 28. September in Moskau statt. Tickets sind momentan die billigsten. Während das Programmkomitee Berichte auswählt, können Sie traditionell Frühbuchertickets für konf kaufen. Nutzen Sie diese Gelegenheit jetzt. Ab dem 29. Juni sind Tickets teurer.


Rahmenverlust


Der Hauptthread führt Code aus, der für Berührungsereignisse und die Arbeit mit der Benutzeroberfläche verantwortlich ist. Er rendert den Bildschirm. Die meisten modernen Smartphones rendern mit 60 Bildern pro Sekunde. Dies bedeutet, dass Aufgaben in 16,67 Millisekunden (1000 Millisekunden / 60 Frames) erledigt werden müssen. Daher ist die Beschleunigung im Hauptgewinde wichtig.


Wenn ein Vorgang länger als 16,67 Millisekunden dauert, tritt automatisch ein Bildverlust auf, und Anwendungsbenutzer werden dies beim Abspielen von Animationen bemerken. Auf einigen Geräten ist das Rendern sogar noch schneller, beispielsweise auf dem iPad Pro 2017 beträgt die Bildschirmaktualisierungsrate 120 Hz, sodass nur 8 Millisekunden erforderlich sind, um Vorgänge in einem Frame abzuschließen.


Regel Nr. 1


CADisplayLink ist ein spezieller Timer, der während der vertikalen Synchronisation (Vsync) startet. Durch die vertikale Synchronisation wird sichergestellt, dass zum Rendern eines Frames nicht mehr als 16,67 Millisekunden zugewiesen werden. Zur Überprüfung in AppDelegate können Sie CADisplayLink in der Hauptlaufschleife registrieren und haben dann eine zusätzliche Funktion, die die Berechnungen ausführt. Sie können die Dauer der Anwendung verfolgen und herausfinden, wie viel Zeit seit dem letzten Start dieser Funktion vergangen ist.


.


Der Start erfolgt, wenn ein Rendering erforderlich ist. Wenn viele verschiedene Operationen ausgeführt wurden, die den Hauptthread überlasteten, beginnt diese Funktion mit einer Verzögerung von 100 Millisekunden. Dies bedeutet, dass zu viel Arbeit geleistet wurde und in diesem Moment Personal verloren ging.


Hier ist die Catstagram App. Während des Herunterladens von Bildern wird die Anwendung langsamer. Wir sehen, dass die Bildrate an einem bestimmten Punkt abnahm und die Ladezeit etwa 200 Millisekunden dauerte. Etwas scheint zu viel Zeit in Anspruch zu nehmen.


.


Benutzer werden davon nicht begeistert sein, insbesondere wenn die Anwendung auf älteren Geräten wie iPhone 5 oder älteren iPod-Modellen usw. ausgeführt wird.


Zeitprofiler


Ein nützliches Tool zum Verfolgen solcher Probleme ist Time Profiler. Andere Tools sind ebenfalls nützlich, aber letztendlich verwenden wir bei Fyusion in 90% der Fälle den Time Profiler. In der Regel hängen Probleme in einer Anwendung mit ScrollView, Bereichen mit Text und Bildern zusammen.


Bilder sind wichtig. Wir dekodieren das JPEG-Format mit UIImage . Sie machen es langsam und wir können ihre Leistung nicht direkt verfolgen. Dies geschieht nicht unmittelbar nach dem Festlegen des Bildes in UIImageView , aber Sie können diesen Moment durch Nachverfolgung in Time Profiler sehen.


Die Textformatierung ist ein weiterer wichtiger Punkt. Es ist wichtig, wenn die Anwendung eine große Menge an "komplexem" Text enthält, beispielsweise in Japanisch oder Chinesisch. Es kann lange dauern, bis die richtigen Größen für Zeilen mit Text berechnet sind.


Das Schnittstellen-Markup verlangsamt auch das Rendern in der Anwendung. Dies gilt insbesondere für das AutoLayout-Tool. AutoLayout ist bequem zu verwenden, verlangsamt jedoch die Anwendung im Vergleich zum manuellen Markup erheblich. Wir müssen Zugeständnisse machen. Wenn AutoLayout die Anwendung verlangsamt, ist es möglicherweise an der Zeit, sie abzubrechen und andere Arten von Markups auszuprobieren.


Spurenmuster




In diesem Beispielaufrufbaum können Sie sehen, welche Art von Arbeit die CPU leistet. Sie können den Trace-Typ ändern und ihn aus der Sicht von Threads und Prozessoren betrachten. Normalerweise ist es am interessantesten, den Trace in Threads aufzuteilen und den Haupt-Thread zu überwachen.


Die anfängliche Spurenanalyse mag kompliziert erscheinen. Es ist nicht immer möglich, sofort herauszufinden, was FRunLoopDoSource0 bedeutet.


Wenn Sie die Spur durchsuchen, können Sie verstehen, wie das System funktioniert, und dann macht alles Sinn. Sie können die Stapelverfolgung verfolgen und alle Systemelemente anzeigen, die Sie nicht geschrieben haben. Ganz unten steht jedoch Ihr Quellcode.


Baum anrufen


Angenommen, wir haben eine sehr einfache Anwendung. Es enthält die Hauptfunktion, die mehrere andere Funktionen aufruft. Die Arbeit von Time Profiler besteht im Wesentlichen darin, dass Snapshots des aktuellen Status der Stapelverfolgung mit einer Häufigkeit von einer Millisekunde (standardmäßig) erstellt werden. Nach einer weiteren Millisekunde macht er eine Momentaufnahme der Spur. Es ruft die Hauptfunktion auf, die die Funktion " foo " aufruft, die die Funktion " bar " nennt. Die anfängliche Stapelverfolgung wird im folgenden Screenshot gezeigt. Diese Daten werden zusammen gesammelt. Gegenüber jeder Funktion wird eine Zahl angezeigt: 1, 1, 1.




Dies bedeutet, dass jede dieser Funktionen einmal aufgerufen wurde. Nach einer Millisekunde erhalten wir eine weitere Aufnahme des Stapels. Diesmal sieht es genauso aus, also erhöhen sich alle Zahlen um 1 und wir erhalten 2, 2, 2.




Während der dritten Millisekunde sieht unser Aufrufstapel etwas anders aus. Die Hauptfunktion ruft bar direkt auf. Daher wird der Hauptfunktion und der bar eine weitere Einheit hinzugefügt, und ihr Wert wird 3. Als nächstes erfolgt eine Trennung. Manchmal ruft die Hauptfunktion direkt " foo " auf, manchmal wird " bar " direkt aufgerufen. Das ist einmal passiert. Eine Funktion wurde durch eine andere aufgerufen.


Als nächstes rief eine Funktion eine andere auf, die die dritte Funktion nannte. Wir sehen, dass die " baz " -Funktion zweimal aufgerufen wurde. Diese Funktion ist jedoch so unbedeutend, dass sie schneller als eine Millisekunde aufgerufen wird.


Bei der Verwendung von Time Profiler ist zu beachten, dass keine bestimmten Zeitintervalle angezeigt werden. Es wird nicht die genaue Ausführungszeit einer Funktion angezeigt. Er gibt nur an, wie oft es auf den Bildern erscheint, was nur einen ungefähren Wert für die Dauer jeder Funktion angibt. Da einige Prozesse schnell genug sind, werden sie auf den Bildern nie angezeigt.




Wenn Sie Anrufe in den Konsolenmodus wechseln, können Sie alle Momente einer Verringerung der Bildrate anzeigen und vergleichen. In dem Beispiel trat der Rahmenverlust mehrmals auf und verschiedene Prozesse wurden durchgeführt.




Durch Klicken bei gedrückter Alt-Taste auf macOS werden der Abschnitt und die Unterabschnitte erweitert, nicht nur der ausgewählte. Sie werden nach dem Arbeitsaufwand sortiert. In 90% der Fälle steht CFRunLoopRun , gefolgt von Rückrufen.


Diese Anwendung basiert vollständig auf einem einzelnen Run Loop-Taskausführungszyklus. Es gibt einen sich endlos wiederholenden Zyklus, und bei jeder Iteration werden Rückrufe gestartet. Wenn Sie sich diese Rückrufe ansehen, können Sie die wichtigsten Engpässe hervorheben.


Wenn Sie sich diese Herausforderungen genauer angesehen haben, werden Sie höchstwahrscheinlich nicht verstehen, was sie tun. Dies können Renderings, Bildanbieter oder E / A sein.




Es gibt eine Option, mit der Sie Systembibliotheken ausblenden können. Sie sind eigentlich die Problembereiche der Anwendung.


Es gibt Messgeräte, die in Prozent angeben, wie viel Arbeit eine bestimmte Funktion oder Operation leistet. Wenn wir uns dieses Beispiel ansehen, sehen wir hier den Wert - 34%. Dies ist der Apple-Prozess jpeg_decode_image_all . Nach dem Studium wird klar, dass die Dekodierung von JPEG-Bildern im Haupt-Thread erfolgt und dies in den meisten Fällen die Ursache für den Bildverlust ist.




Regel Nr. 2


Im Allgemeinen sollte das Dekodieren von JPEG-Bildern im Hintergrund erfolgen. Die meisten Bibliotheken von Drittanbietern (AsyncDisplayKit, SDWebImage usw.) können dies standardmäßig tun. Wenn Sie keine Frameworks verwenden möchten, können Sie die Dekodierung manuell durchführen. Dazu können Sie eine Erweiterung über UIImage schreiben, in der Sie einen Kontext erstellen und manuell ein Bild zeichnen.




Wenn Sie diesen Vorgang ausführen, können Sie die Funktion decodeImage nicht vom decodeImage aus aufrufen. Es wird immer ein dekodiertes Bild zurückgegeben. Es gibt keine Möglichkeit zu überprüfen, ob ein bestimmtes UIImage-Bild die Dekodierung bestanden hat. Sie müssen sie daher immer durch diese Methode führen. Wenn Sie die Daten jedoch korrekt zwischenspeichern, gibt es keine unnötigen Prozesse im System.


Aus technischer Sicht ist dies weniger effektiv. Die Verwendung der UIImageView Klasse scheint optimiert und effizient zu sein. Es führt aber auch eine Hardware-Dekodierung durch, sodass es auch seine Nachteile hat. Mit dieser Methode werden Ihre Bilder langsamer dekodiert. Aber es gibt gute Nachrichten - Sie können das Bild auf die oben beschriebene Weise dekodieren, nicht im Hauptthread, und dann zum Hauptthread zurückkehren und die Schnittstelle konfigurieren.




Trotz der Tatsache, dass dieser Vorgang mehr Zeit benötigt, kann er möglicherweise nicht für den Hauptthread ausgeführt werden, was bedeutet, dass die Benutzeraktivität in der Anwendung nicht beeinträchtigt wird, da das Scrollen des Bandes nicht verlangsamt wird. Profitable Lösung.


Warnungen wegen Speichermangel


Bei jedem Signal mit wenig Speicher möchte ich alle nicht verwendeten Daten löschen, die möglich sind. Wenn jedoch verschiedene Prozesse in Streams von Drittanbietern ausgeführt werden, nimmt die Platzierung von volumetrisch decodierten JPEG-Bildern auf diesen den größten Teil des freien Speicherplatzes ein.


Ein solches Problem trat in der Fyuse-Anwendung auf. Wenn ich alle meine JPEG-Bilder in einem Stream eines Drittanbieters dekodiert hätte, würde dieses System in einigen Fällen, beispielsweise bei älteren Telefonmodellen, die Anwendung sofort beschädigen. Dies liegt an der Tatsache, dass Taskflows von Drittanbietern nicht auf eine Warnung über unzureichenden Arbeitsspeicher vom System reagieren, z. B. "Hey, unnötige Daten löschen!". Die folgende Situation tritt auf: Zuerst platzieren Sie alle diese Bilder in Streams von Drittanbietern, und dann stürzt die Anwendung ständig ab. Wenn Threads von Drittanbietern Signale an das Hauptthread senden, was im System geschieht, tritt ein solches Problem nicht auf.


Arbeiten Sie ohne Fehler




Der Hauptthread ist im Wesentlichen eine Warteschlange, die aus Prozessen besteht. Wenn Sie mit Threads von Drittanbietern arbeiten, können Sie den Befehl performSelectorOnMainThread:withObject:waitUntilDone: in Objective-C schreiben. Dank ihr werden Aufgaben im Hauptthread an das Ende der Warteschlange gestellt. Wenn der Hauptthread gerade mit der Verarbeitung von Benachrichtigungen aufgrund von Speichermangel beschäftigt ist, können Sie durch Aufrufen dieses Befehls warten, bis alle Benachrichtigungen verarbeitet wurden, und erst dann mit dem komplexen Prozess des Ladens und Platzierens von Daten beginnen. In Swift sieht das etwas einfacher aus. DispatchQueue.main.sync Speicherplatz im Hauptthread frei.


Hier ist ein weiteres Beispiel. Wir haben Speicher freigegeben und Bilder in Streams von Drittanbietern dekodiert. Das visuelle Scrollen des Bandes ist viel besser geworden. Wir verlieren immer noch Frames, weil wir den iPod 5g testen. Dies ist eines der schlechtesten Testmodelle derjenigen, die noch iOS-Versionen 10 und 11 unterstützen.




Wenn diese Art von Bildverlust auftritt, können Sie das Band trotzdem anzeigen. Es bleiben jedoch Prozesse, die weiterhin zu Personalverlusten führen. Es gibt andere Möglichkeiten, die Anwendung schneller auszuführen.


Natürlich ist es nicht immer einfach, die Anwendung zu optimieren. Wenn Sie jedoch Aufgaben haben, deren Ausführung relativ lange dauert, sollten Sie sie in die Hintergrundthreads einfügen. Stellen Sie sicher, dass diese Aufgaben nicht mit der Benutzeroberfläche zusammenhängen, da viele UIKit-Klassen nicht threadsicher sind, dh Sie können sie nicht im Backend erstellen.


Verwenden Sie Core Graphics, wenn Sie Bilder in einem Stream eines Drittanbieters verarbeiten müssen. Blenden Sie die Anzeige von Systembibliotheken nicht aus. Denken Sie an die Warnungen wegen Speichermangels.


Willkommen bei MBLT DEV 2018


Kommen Sie am 28. September zur 5. Internationalen MBLT DEV 2018 Mobile Developers Conference nach Moskau. Die ersten Redner sind bereits vor Ort und der neueste Frühaufsteher ist noch im Verkauf. Die Ticketpreise steigen am 29. Juni. Kaufen Sie jetzt Tickets zum niedrigsten Preis.



Lesen Sie im zweiten Teil des Artikels, den wir am 28. Juni veröffentlichen werden, mehr über die Implementierung der Benutzeroberfläche in iOS, die Verwendung von Bezier-Kurven und andere nützliche Tools.

Source: https://habr.com/ru/post/de415155/


All Articles