Interessante Optimierung des Compilers oder Zufall?

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

  • Interessante Optimierung des Compilers oder Zufall?

    Hallo,

    ich habe Folgendes beobachtet (Xcode 4.5.2, iPad-Simulator) und kann es mir nicht erklären: In einer Methode werden zunächst einige (10-15) double-Variablen berechnet, die allerdings statisch und nicht von Übergabeparametern oder sonstigen änderbaren Variablen abhängig sind. Anschließend werden mit diesen Werten und den Übergabeparemetern per Saldo rund 50 double-Operationen (Plutimikationen und Additionen, sin, cos, tan, sqrt, pow in einer wilden mathematischen Orgie) durchgeführt.

    Ich habe den Aufruf der Methode in zwei [NSDate date]-Aufrufe geschachtelt, weil ich wissen will, wie schnell das ist.

    Phänomen 1: Beim ersten Aufruf dauert der Durchlauf z.B. 0.0000312s. Bei allen folgenden Durchläufen, auch wenn sich die Werte der Parameter ändern, z.B. 0.0000036s - also 10x schneller! Warum?

    Phänomen 2: Ich habe die "statischen" Variablen berechnet und durch #defines ersetzt. M.E. das Beste, wenn man Variablen-Overhead reduzieren möchte, weil die Dinger im Präprozessor as-is eingefügt werden. Egal. Im Ergebnis fallen also die besagten 10-15 double-Berechnungen weg. Wenn ich nun das Ding starte, ist das Laufzeitverhalten identisch - ja, identisch!! - zum vorigen Durchgang (s. Phänomen 1). Das heißt, dass der Durchgang z.B. erst 0.000034s und dann immer 0.000003s dauert. Aber waurm??

    Ich versteh es nicht.

    #Edit: Auf einem iPad mini gleiches Verhalten, nur etwas langsamer: 0.0004s zu 0.00002s

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

  • 1. Genau was macmoonshine sagt. nimm clock() oder etwas highperformance. Und lass das ganze 10000 mal laufen. 0.0000312s sagt so ziemlich gar nichts aus.

    2. Niemals #define fuer soetwas. Der Compiler ist ja nicht bloed. Um sicher zu gehen, nimm "const double v = 123.;", Objective-C sollte ja auch const-Konstanten koennen. Der Hauptgrund ist, dass #define eigentlich nur nachteile hat, wie etwa keine Typsicherheit und Du Dir schnell Probleme einholst.

    3. Wenn das ganze nach inkorrektem Timing schon weniger als eine Millisekunde braucht, warum investierst Du ueberhaupt Zeit in die Optimierung?
    C++
  • Unabhängig von der Messmethode spielen hier sicherlich auch noch Caching-Effekte eine Rolle. Beim ersten Aufruf der Methode müssen Code und Daten in den Cache geladen werden, was natürlich etwas Zeit benötigt. Bei den Folgeaufrufen sind die Daten schon im Cache vorhanden, folglich keine Cache Misses, was widerum bedeutet, dass der Code schneller ausgeführt werden kann.
  • Pennywise81 schrieb:

    Unabhängig von der Messmethode spielen hier sicherlich auch noch Caching-Effekte eine Rolle. Beim ersten Aufruf der Methode müssen Code und Daten in den Cache geladen werden, was natürlich etwas Zeit benötigt. Bei den Folgeaufrufen sind die Daten schon im Cache vorhanden, folglich keine Cache Misses, was widerum bedeutet, dass der Code schneller ausgeführt werden kann.

    Naja, schwer zu sagen. Bei einem Loop hast Du natürlich recht. Bei einem Funktionsaufruf mit wilden calls ([NSDate date]) davor/dahinter, würde ich eher davon ausgehen, dass Dir der gesamte Cache schon wieder zerstört wurde (zumindest L1).
    Aber bei 30 vs. 3 MICROsekunden, ich weiss nich, da kann man eher wenig Aussagen.

    Oh, nebenbei, der Emulator ist ja eine VM (LLVM wahrscheinlich sogar) [EDIT: Oh, ist er das? Weiss gar nicht mehr, mach ja kein iOS Dev -- Führt der ARM code aus, oder x86?]. Der macht eventuell auch zur Laufzeit weitere Optimierung (branch-prediction etc). Googles V8 macht das zumindest, darum bekommt man theoretisch JavaScript-Code schneller als selbstgeschriebenen C-Code :P
    C++
  • Nein, es geht mir nicht darum, etwas zu optimieren, sondern darum, das ich gern verstehen würde, warum sich das Szenario so für mich unerwartet verhält.

    Der Hinweis auf die #defines ist sehr wertvoll, Danke! Für "clock()" und "time(3)" bin ich aber zu blöd ;-/

    Im Übrigen: Was, wenn die Berechnung 10.000 laufen soll ...? Optimierung ist schon OK für mich, habe ich immer gemacht und werde ich immer machen. Ich mag mich beim Design der Effizienz meiner Algorithmen einfach nicht auf das Mooresche Gesetz verlassen.
  • zerm schrieb:

    Pennywise81 schrieb:

    Unabhängig von der Messmethode spielen hier sicherlich auch noch Caching-Effekte eine Rolle. Beim ersten Aufruf der Methode müssen Code und Daten in den Cache geladen werden, was natürlich etwas Zeit benötigt. Bei den Folgeaufrufen sind die Daten schon im Cache vorhanden, folglich keine Cache Misses, was widerum bedeutet, dass der Code schneller ausgeführt werden kann.

    Naja, schwer zu sagen. Bei einem Loop hast Du natürlich recht. Bei einem Funktionsaufruf mit wilden calls ([NSDate date]) davor/dahinter, würde ich eher davon ausgehen, dass Dir der gesamte Cache schon wieder zerstört wurde (zumindest L1).
    Aber bei 30 vs. 3 MICROsekunden, ich weiss nich, da kann man eher wenig Aussagen.

    Bei einem Faktor 10 sieht es für mich sehr stark nach Cache aus. Ich gehe auch einfach mal davon aus, dass der Code aus einer Loop ausgeführt wurde. Selbst wenn nicht, so bleibt immer noch eine hohe Wahrscheinlichkeit, dass Daten im Cache liegen. Gerade in dem Fall von fwtag sieht es so aus, dass er eine bestimmte Funktion/Methode immer wieder auf die Laufzeit testet. Andere große Aktionen werden da bestimmt nicht ausgeführt. Wenn ich jetzt einen Intel Core 2 Duo als Grundlage nehme, so haben wir 32 kB L1 Cache pro Kern und 2 MB L2 Cache. Das ist schon eine enorme Menge, die ausreicht um eine wiederkehrende Funktion stark zu "optimieren".
    Letztendlich gebe ich dir aber auch Recht, dass es auch auf den Umfang des Programms ankommt.


    Oh, nebenbei, der Emulator ist ja eine VM (LLVM wahrscheinlich sogar) [EDIT: Oh, ist er das? Weiss gar nicht mehr, mach ja kein iOS Dev -- Führt der ARM code aus, oder x86?]. Der macht eventuell auch zur Laufzeit weitere Optimierung (branch-prediction etc). Googles V8 macht das zumindest, darum bekommt man theoretisch JavaScript-Code schneller als selbstgeschriebenen C-Code :P

    Der Emulator ist aber kein Emulator, sondern ein Simulator... entsprechend wird x86 Code ausgeführt. Deshalb läuft in der Regel auch der Code im Simulator schneller als auf dem tatsächlichen iDevice.
  • Ihr hattet übrigens Recht: Ich habe meinen ersten Versuch mit NSDate und meinen zweiten Versuch mit CFAbsoluteTimeGetCurrent() durch die von macmoonshine vorgeschlagene clock()-Methodik ersetzt und bekomme nun nicht mehr dieses Phänomen, dass der erste Durchlauf 10x länger braucht als die übrigen. Es scheint also ein Cache-Thema zu sein, allerdings aufgrund meiner Zeitmessungen und nicht der Methodik an sich.