er Software-Entwickler und KI-Experte Olaf van Zon hat triqtraq programmiert. Dabei musste er insbesondere für guten Klang sorgen – wegen der Rechenleistung des iPhones immer noch die größte Herausforderung an einer Musik-App.
Making-of
Audio-Applikationen verlangen vor allem eine starke Prozessorleistung: Um Audio in CD-Qualität zu erzeugen, benötigt man ein ausgehendes Signal von 44 100 Kilohertz mit einer Präzision von 16 Bit. Das bedeutet, dass die App jede Sekunde 44 100 Abtastwerte (die kleinsten unterscheidbaren Teilchen im Audiobereich) in Stereo generieren muss. Bei triqtraq haben wir vier Kanäle, also die vierfache Rechenlast, dazu kommen die verschiedenen Algorithmen für Decay und Filter; zum Mixen und fürs Panning der Kanäle brauchen wir außerdem CPU-Leistung. Alles in allem mussten wir den Code also optimieren, damit keine Latenzen entstehen, und ich verrate hier ein paar Tricks dazu.
Zunächst suchte ich mir die Stellen im Code, bei denen es wirklich auf Geschwindigkeit ankommt, und konzentrierte mich auf die Soundverarbeitung. Zum Vergleich: Beim Interface spielt Geschwindigkeit eine eher unbedeutende Rolle, denn hier werden Nutzer eine Verzögerung von 50 Millisekunden kaum bemerken, beim Sound aber sehr wohl. In Apples Editor Xcode 4 findet man unter »Menu Open Developer Tool« ein Werkzeug namens Instruments, mit dem sich rechenintensive Stellen recht gut aufstöbern lassen: Es bietet einen Time Profiler, der abliest, welche Code-Zeile wie viel Verarbeitungszeit benötigt. Nachdem ich also wusste, welche Stellen ich optimieren musste, fing ich mit einfachen Maßnahmen an.
1. Werte in Variablen schreiben
Wird ein Wert öfter als einmal berechnet, ist es besser, ihn in eine Variable zu packen, statt ihn immer wieder neu zu berechnen. Das verringert die CPU-Auslastung auf Kosten von etwas Speicherkapazität. In einfachen Fällen achtet der Compiler auf solche Dinge, die komplexeren Angelegenheiten lassen sich damit aber nicht lösen. Zum Beispiel lassen sich die Code-Zeilen
float outputLeft = A1 * factor * maxVolume;
float outputRight = A2 * factor * maxVolume;
besser so notieren:
float volumeFactor = factor * maxVolume;
float outputLeft = A1 * volumeFactor;
float outputRight = A2 * volumeFactor;
2. ivisionen vermeiden
Da Floating Point Divisions – das Teilen von Gleitkommazahlen – auf dem iPhone ganz schlecht performen, sollte man sie im Audiocode so weit wie möglich vermeiden. Sollte man sie jedoch wirklich brauchen, kann man den Code auf so wenig Divisionen wie möglich reduzieren, etwa so: Der Abschnitt
float someValue = 7.0f;
for (int i = 0; i < 1024; i ++) {
…
float A = x / someValue;
…
}
lässt sich ändern in:
float someValue = 7.0f;
float inverseOfSomeValue = 1 / someValue;
for (int i = 0; i < 1024; i ++) {
…
float A = x * inverseOfSomeValue;
…
}
3. Methoden schneller abrufen
Objective-C kann für zeitkritische Anwendungen relativ ineffizient sein, gerade wenn es um den Aufruf von Methoden geht. Will man eine Methode in einem zeitkritischen Code aufrufen, könnte man die Methode in eine C/C++-Funktion ändern, weil ihr Aufruf viel schneller ist. Objective-C und C/C++ lassen sich gut mischen – doch sollte man dabei Folgendes beachten: Wenn man C++-Code in eine Objective-C-Klasse schreibt, muss man die Dateiendung von ».m« in ».mm« ändern, damit der Compiler den C++-Code erkennt.
-(int)doB {
…
// do something in Objective-c here
…
return someValue;
}
-(void)doA {
x = [self doB];
}
Wenn man doB als C-Funktion schreibt, lässt sie sich als
C-Funktion aufrufen:
int doB (){
…
// do something in C code here
…
return someValue;
}
-(void)doA {
x = doB();
}
4. methodForSelector nutzen
Statt den Code in eine C/C++-Funktion zu programmieren wie im vorherigen Beispiel, können Sie auf
die Objective-C-Methode auch mit einem Zeiger verweisen und sie später mit ihm aufrufen. Das Tempo ist jetzt fast so hoch wie ein Aufruf in C/C++. Sie können den Code also wie folgt optimieren:
typedef int (*DoB_IMP)(id,SEL);
SEL doBSel;
DoB_IMP doBMethod;
-(int)doB {
…
// do something in Objective-C here
…
return someValue;
}
-(void)doA {
doBMethod(self, doBSel);
}
-(void)init {
// storing pointer to function
doBSel = @selector(doB);
doBMethod = (DoB_IMP)[self methodForSelector:doBSel];
}
Nun sind Sie an der Reihe, es selbst zu versuchen. Wenn es nicht auf Anhieb klappt, haben Sie Geduld – die Entwicklung von Apps erfordert viel Zeit und Konzentration, aber das Resultat kann sich durchaus lohnen.
(veröffentlicht in PAGE 12.2012)