Low-level iOS, hogyan lehetne gyorsabb?
A mai Meetup-on tartott előadásommal kapcsolatos fejlesztői infók, kódokkal.
- Látványos eredmény megfelelő szálkezeléssel, UIKit frissítés optimalizációval és ARM Assembly-vel érhető el.
- A szoftver a király: sokkal több erőforrás takarítható meg jó kóddal, mint mondjuk egy új szuper GHz kétmagos CPU-val.
- Ha egyébként rendben van a kód. Például használjunk "tiszta" C-t Objective-C objektumok helyett, ha lehetséges.
Hogyan mérjük a terhelést?
- Instruments: elsősorban RAM, szivárgás és Core Animation mérésre. CPU-hoz nem jó, abból eleve sokat eszik, az apró különbségek nem látszanak.
- Csak a teljes, rendszerszintű idle idő számít. Mérése kódból megoldható (csatoltam).
- A felhasználó a teljes rendszer terhelését érzi, nem az alkalmazásunkét. Könnyű "átvinni" a terhelést a saját kódból az OS-hez (user % -> sys %), de ez nem szabadít fel semmit.
Ökölszabály
25-30% szabad CPU mindig kell, különben "akadozik" az élmény.
NSRunLoop
- A main thread NSRunLoop-ja fontos. Többek között ez fogadja az érintés és egyéb felhasználói eseményeket.
- Ha nincs 25-30% szabad CPU, akkor későn fut, későn érkeznek az események - akad.
- Main thread/main run loop blokkolására ügyelni, események lehető leggyorsabb feldolgozása, külön thread, ha kell.
Szálkezelés
- Fontos a megfelelő prioritás, időszelet (mennyi CPU-t használhat adott időtartam alatt) és stratégia (FIFO, RR, OTHER) beállítása.
- Nincs rá jó SDK megoldás (NSThread setThreadPriority nem megfelelő), hibrid mach/pthread kód kell. Csatoltam.
- Ezzel lehetséges olyan szál futtatása, ami csak a "szabadidőben" fut (idle CPU).
UIKit
- Egy UIKit objektum (pl. UIButton) módosítása "költséges", az UIKit a legtöbb esetben blokkol, amíg be nem fejezte a grafikát.
- Összegyűjteni a módosításokat (pl. UIButton.highlighted), és végrehajtani később, nem a touch event kezelésekor.
- CADisplayLink! Ezzel lehet időzíteni a frissítéseket.
- Az iPhone utálja az átlátszóságot, alpha csatornákat, belassul.
- A legtöbb objektum átlátszatlan legyen, opaque-ra Interface Builder-ben pipa (igen, végig kell kattintgatni az összeset).
- Még így se "halmozzuk egymásra" az objektumokat.
- Ha opaque egy objektum, de alpha-s PNG kép van benne, akkor átlátszó lesz így is...
- Irtsuk ki az alpha csatornát a felhasznált képeknél, ha lehet.
- A szövegek, UILabel módosítása költséges. Saját drawRect metódus és [self.text drawInRect: vagy CGContextShowTextAtPointsokat segít.
- A saját drawRect metódusok sokszor gyorsabbak.
- Instruments - Core Animation - Flash Updated Regions. Meg fogsz lepődni.
Thumb mód
- ARM CPU-k, kétféle futási mód: Thumb és ARM.
- Thumb mód: kisebb kód és más utasításkészlet, ezért kisebb memória sávszél igény - gyorsabb futás, régi eszközökön (nem iPhone).
- Nem tud lebegőpontos számítást, ilyenkor a fordító átkapcsolja a CPU-t ARM módba, aztán pedig vissza. Ez lassú.
- iOS fordító: a Thumb mód alapértelmezett, kapcsoljuk ki. Iterációknál sokat számíthat.
Iterációk optimalizálása - ha az Assembly "túl sok" lenne
- Nézzük meg, hogy a compiler milyen kódot gyárt.
- Elé és mögé inline asm sorok ( __asm__ volatile ("#innen"); ), majd pedig Build - Show Assembly Code.
- Próba: "register" kulcsszó néhány fontos változónál, mennyit változik az Assembly kód. Nem kell annyit töltögetni a memóriából.
- A compiler nem mindig jó a regiszterek hatékony elosztásában, csak néhányat használ.
- Egyszeres lebegőpontos számokat érdemes használni (sima, 32-bites float), hogy a compiler használja a lebegőpontos egységet.
- A memóriahozzáférés sokkal drágább, mint az asztali gépekben. A memcpy is!
Assembly
- XCode-ban lehet inline Assembly-t írni.
- A C-változatot is meg kell hagyni, mert a Simulatorhoz felesleges külön Assembly verzió. A Simulator x86-os, a régebbi iOS eszközök-ok ARM6-os (VFP), iPhone 3GS-től felfelé pedig ARM7-es (Neon) kódot kérnek.
- Universal binary kell (Build Settings - Architectures - Standard (armv7 armv7), illetve -mfloat-abi=softfp -mfpu=neon kapcsolók (Other C Flags).
- Az Assembly-be átírás nem triviális, ne "automatikusan" csináljuk. Jó Assembly kód csak "kézműves" munka eredménye lehet.
- A regiszterek hatékony kihasználása, a lehető legkevesebb memóriahozzáférés (és lehetőleg blokkban), valamint az utasítások ciklusszámának ismerete sokszoros sebességkülönbséget jelent!
- Minden utasítást adott számú ciklus alatt hajt végre a processzor, de képes többet párhuzamosan is!
- Használjuk az "egybeépített" segédprocesszort, lebegőpontos számításokra (32 bit float), főleg vektorokhoz, mátrixokhoz. Egy ilyen szorzás pl. 8 ciklus alatt megy végbe, ez idő alatt mást is csinálhat a CPU, ha a következő utasítás nem a szorzás által használt regisztereken dolgozik (párhuzamosítás).
Példa: DJ PLAYER keverő (mixer) kód iPhone 2G-n
- C: 18% CPU.
- VFP Assembly, a ciklusszámok figyelembe vétele nélkül: 8% CPU.
- Végleges, optimalizált változat: 2% CPU.
Kódok
Szálkezelés. Idle CPU mérés.