1. Nimm jetzt an unserem 2. ADVENT-Gewinnspiel teil - Alle Informationen findest Du hier!

Fließkomma-Performance

Dieses Thema im Forum "Android App Entwicklung" wurde erstellt von Jarny, 21.12.2009.

  1. Jarny, 21.12.2009 #1
    Jarny

    Jarny Threadstarter Android-Experte

    Beiträge:
    520
    Erhaltene Danke:
    37
    Registriert seit:
    01.06.2009
    Hi
    Ich hab mich heute mal ein bisschen mit Programmierung unter Android beschäftigt.
    Unter anderem hab ich mir ein kleines Testprogramm geschrieben was sehr viele Fließkomma-Operationen ausführt. Ich benutze dabei ausschliesslich den Datentyp double.
    Das Programm rechnet den Umfang der Erdkugel am Äquator aus indem ich 360000 mal kleine Teilstückchen aufsummiere. Die Teilstückchen berechnen sich aus einer sehr komplexen Formel (6 mal sin/cos Operationen, 1 mal arctan, 2 mal sqrt, 29 Multiplikationen, 7 Divisionen und ungezählte Additionen) pro Iteration!!!

    Wie erwartet kommt bei der Berechnung 40075035m raus aber das interessante ist die Performance die ich gemessen habe. Gemessen wurde die reinen Berechnungen ohne Bildschirmausgabe:

    • Auf meinem Laptop (CoreDuo 1.6Ghz, 2 GByte) mit Java implementiert: 182ms
    • Auf dem Emulator: 31104 ms
    • Auf meinem Milestone: 4168 ms
    Dafür, dass die c't in ihrem Artikel im Mai noch geschrieben hat, dass man tunlichst keine Fließkommaberechnungen machen sollte finde ich die Performance erstaunlich gut.
    Wie gesagt, die Berechnungen wurden in double-Genauigkeit durchgeführt und nicht in float (Gibts für float überhaupt ne Lib mit den Trigonometrische Funktionen?)

    Was habt ihr denn so für Erfahrungen mit Fließkomma-Arithmetik gemacht? Sind die älteren Geräte wirklich so schwach bei Fließkommaberechnungen wie die c't schreibt? Evtl. kann mal jemand das Programm mit nem G1 oder Magic austesten.

    Hier ist der Source:
    Code:
    package com.example.helloandroid;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.TextView;
    
    public class HelloAndroid extends Activity {
    	/** fuer die Distanzberechnung */
    	static final double cdblRad = Math.PI / 180;
    	static final double cdblAbpl = 1 / 298.257223563; // Abplattung der Erde
    	static final double cdblAeq = 6378140; // Äquatorradius der Erde (m)
    
    	/** Called when the activity is first created. */
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		TextView tv = new TextView(this);
    
    		tv.setText("Hello world");
    
    		setContentView(tv);
    
    		double dStep = 0.001;
    		double dWinkel = 0.0;
    		double dStrecke = 0.0;
    
    		long lStart = System.currentTimeMillis();
    
    		for (int i = 0; i < 360000; i++) {
    			double dPrevWinkel = dWinkel;
    			dWinkel += dStep;
    
    			// Teilstrecke berechnen
    			double dTeilstrecke = calcDist(dPrevWinkel, 0, dWinkel, 0);
    
    			dStrecke += dTeilstrecke;
    		}
    
    		long lEnde = System.currentTimeMillis();
    
    		tv.setText("Zeit:" + (lEnde - lStart) + " millis");
    	}
    
    	
    	public double calcDist(double dblLng1, double dblLat1, double dblLng2, double dblLat2) {
    		double b1 = dblLat1;
    		double l1 = dblLng1;
    
    		double b2 = dblLat2;
    		double l2 = dblLng2;
    
    		double F = (b1 + b2) / 2 * cdblRad;
    		double G = (b1 - b2) / 2 * cdblRad;
    		double l = (l1 - l2) / 2 * cdblRad;
    
    		double dblSinl = Math.sin(l);
    		double dblSinlq = dblSinl * dblSinl;
    
    		double dblCosl = Math.cos(l);
    		double dblCoslq = dblCosl * dblCosl;
    
    		double dblSinG = Math.sin(G);
    		double dblCosG = Math.cos(G);
    
    		double dblSinF = Math.sin(F);
    		double dblCosF = Math.cos(F);
    
    		double S = dblSinG * dblSinG * dblCoslq + dblCosF * dblCosF * dblSinlq;
    		double C = dblCosG * dblCosG * dblCoslq + dblSinF * dblSinF * dblSinlq;
    		double w = Math.atan(Math.sqrt(S / C));// in rad
    
    		if (w == 0.0)
    			return 0.0;
    
    		double R = Math.sqrt(S * C) / w;
    		double D = 2 * w * cdblAeq;
    		double H1 = (3 * R - 1) / (2 * C);
    		double H2 = (3 * R + 1) / (2 * S);
    
    		double s = D * (1 + cdblAbpl * H1 * dblSinF * dblSinF * dblCosG * dblCosG - 
    				   cdblAbpl * H2 * dblCosF * dblCosF * dblSinG * dblSinG);
    		return s;
    	}
    
    }
    
    Gruß
    Jarny
     
  2. Shinigami, 23.12.2009 #2
    Shinigami

    Shinigami Fortgeschrittenes Mitglied

    Beiträge:
    436
    Erhaltene Danke:
    69
    Registriert seit:
    08.04.2009
    Hey ho,

    interessantes Experiment. Habe mich mit so einer Thematik übrigens nie beschäftigt, aber ich steuer mal ein paar Vergleichswerte bei.

    Auf meinem G1 (mit cyanogen Mod drauf und daher auf 528Mhz laufend) braucht's 14820ms. Schon ein "leichter" Unterschied zum Milestone. Würde jetzt gern das Ergebnis noch auf'n Acer Liquid sehen oder auf'n Nexus One :)

    (Auf meinem AMD Athlon X2 mit 2x2Ghz läuft's übrigens 178ms und im Emulator über 54700ms)

    Gruß,
    Shini
     
  3. Luise Lustig, 23.12.2009 #3
    Luise Lustig

    Luise Lustig Android-Experte

    Beiträge:
    835
    Erhaltene Danke:
    202
    Registriert seit:
    16.08.2009
    nette Idee :)
    schwanken die Werte bei euch auch so stark?

    das Galaxy meldet meist zwischen 17000-18000 ms, manchmal auch bis zu 21000 ms hoch ... aktuelle Firmware, Android 1.5

    Emulator um die 32000-33000 ms ... Macbook mit 2 GHz Core2Duo, JDK 1.6.0_17, Android SDK 1.5 R3, IntelliJ IDEA 9 mit Android Plugin

    edit:
    der Mac mit Java direkt (ebenfalls JDK 1.6.0_17) meldet 130-140 ms
     
    Zuletzt bearbeitet: 23.12.2009
  4. badlogic, 24.12.2009 #4
    badlogic

    badlogic Neuer Benutzer

    Beiträge:
    15
    Erhaltene Danke:
    6
    Registriert seit:
    23.12.2009
    Kann die Werte auch auf meinem Milestone bestätigen.

    Meiner Erfahrung nach reicht die Performance mit eionfacher Genauigkeit für viele in der Spieleprogrammierung verwendeten Konstrukte vollkommen aus. Anstatt der normalen Math.xxx Methoden die intern mit Doubles rechnen sollte man jedoch auf FloatMath zurückgreifen. Die Klasse bietet mehrere statische Methoden für Trigonometrie und andere Dinge an, rechnet dabei aber mit single precision und ist um einiges schneller.

    Lustigerweise bringt fixed point Arithmetik meiner Erfahrung nach keine Gewinne. Grund dafür dürfte wohl die etwas schwachbrüstige VM sein.
     
  5. Jarny, 25.12.2009 #5
    Jarny

    Jarny Threadstarter Android-Experte

    Beiträge:
    520
    Erhaltene Danke:
    37
    Registriert seit:
    01.06.2009
    Heftig, mehr als 3 mal so schnell (und hättest du das G1 nicht overclocked wäre es wahrscheinlich sogar Faktor 4).

    Den Tipp von badlogic mit der floatmath-Lib werd ich mal ausprobieren und alles auf Float umstellen. Leider fehlt die atan-Funktion in der Lib :-( , so dass er wieder zeitaufwendig double <-> float Konvertierungen machen muss. Werde es aber trotzdem interessehalber umschreiben.
    Vielleicht sind dann auch die G1 (bzw. Qualcomm MSM7201A) Nutzer nicht mehr um Faktor 4 langsamer.

    Also hier aufm Milestone schwanken die Werte nicht ganz so stark. Kommt natürlich immer drauf an was sich gerade im Hintergrund zufällig so abspielt. Man weiss ja nie, ob der GC nicht gerade mal wieder aufräumt oder einer der anderen laufenden internen Systemprozesse irgendwas tun muss.

    Gruß
    Jarny
     
  6. Jarny, 25.12.2009 #6
    Jarny

    Jarny Threadstarter Android-Experte

    Beiträge:
    520
    Erhaltene Danke:
    37
    Registriert seit:
    01.06.2009
    Habs mal auf die FloatMath-Version umgeschrieben:
    Code:
    package com.example.helloandroid;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.TextView;
    import android.util.Log;
    import android.util.FloatMath;
    
    
    public class HelloAndroid extends Activity {
    	/** fuer die Distanzberechnung */
    	static final float cdblRad = 3.14159265358979323f / 180.0f;
    	static final float cdblAbpl = 1 / 298.257223563f; // Abplattung der Erde
    	static final float cdblAeq = 6378140f; // Äquatorradius der Erde (m)
    
    	/** Called when the activity is first created. */
    	@Override
    	public void onCreate(Bundle savedInstanceState) 
    	{
    		super.onCreate(savedInstanceState);
    		
    		TextView tv = new TextView(this);
    
    		tv.setText("Hello world");
    		
    		setContentView(tv);
    
    		float dStep = 0.001f;
    		float dWinkel = 0.0f;
    		float dStrecke = 0.0f;
    
    		long lStart = System.currentTimeMillis();
    
    		for (int i = 0; i < 360000; i++) 
    		{
    			float dPrevWinkel = dWinkel;
    			dWinkel += dStep;
    
    			// Teilstrecke berechnen
    			float dTeilstrecke = calcDist(dPrevWinkel, 0, dWinkel, 0);
    
    			dStrecke += dTeilstrecke;
    		}
    
    		long lEnde = System.currentTimeMillis();
    
    		tv.setText("Zeit:" + (lEnde - lStart) + " millis" + " Strecke:" + dStrecke);
    	}
    
    	
    	public float calcDist(float dblLng1, float dblLat1, float dblLng2, float dblLat2) {
    		float b1 = dblLat1;
    		float l1 = dblLng1;
    
    		float b2 = dblLat2;
    		float l2 = dblLng2;
    
    		float F = (b1 + b2) / 2 * cdblRad;
    		float G = (b1 - b2) / 2 * cdblRad;
    		float l = (l1 - l2) / 2 * cdblRad;
    
    		float dblSinl = FloatMath.sin(l);
    		float dblSinlq = dblSinl * dblSinl;
    
    		float dblCosl = FloatMath.cos(l);
    		float dblCoslq = dblCosl * dblCosl;
    
    		float dblSinG = FloatMath.sin(G);
    		float dblCosG = FloatMath.cos(G);
    
    		float dblSinF = FloatMath.sin(F);
    		float dblCosF = FloatMath.cos(F);
    
    		float S = dblSinG * dblSinG * dblCoslq + dblCosF * dblCosF * dblSinlq;
    		float C = dblCosG * dblCosG * dblCoslq + dblSinF * dblSinF * dblSinlq;
    		float w = (float) Math.atan( (double) FloatMath.sqrt(S / C) );// in rad
    
    		if (w == 0.0)
    			return 0.0f;
    
    		float R = FloatMath.sqrt(S * C) / w;
    		float D = 2 * w * cdblAeq;
    		float H1 = (3 * R - 1) / (2 * C);
    		float H2 = (3 * R + 1) / (2 * S);
    
    		float s = D * (1 + cdblAbpl * H1 * dblSinF * dblSinF * dblCosG * dblCosG - 
    				   cdblAbpl * H2 * dblCosF * dblCosF * dblSinG * dblSinG);
    		return s;
    	}
    
    }
    
    Speed:
    Auf dem Emulator läufts jetzt knapp 30% schneller: 26102 ms
    Auf dem Milestone: 4066 ms d.h. genauso schnell wie die double-Version

    Das find ich ein echt heftiges Ergebnis! Der OMAP 3430 scheint keine Optimierung für float-Berechnungen zu haben. Also Erkenntnis für mich: Für die aktuelle Prozessorgeneration in Android-Smartphones bringt es nichts die Berechnungen mit float-Datentypen zu optimieren.
    Zweite Erkenntnis: Das Ergebnis der Float-Berechnung weist zudem noch einen ziemlichen Rechenfehler auf. 40221696m anstatt 40075035m. Immerhin fast 0.4% Abweichung :-(

    Vielleicht kann jemand mit nem G1 den obenstehenden Algo nochmal durchlaufen lassen. Nur mal um zu sehen, wieviel die Umstellung auf Float-Datentypen bei der ''älteren'' Prozessoren bringt.
    Ne Messung mit dem Snapdragon wäre auch noch interessant.

    Gruß
    Jarny
     
  7. Jarny, 16.05.2010 #7
    Jarny

    Jarny Threadstarter Android-Experte

    Beiträge:
    520
    Erhaltene Danke:
    37
    Registriert seit:
    01.06.2009
    Hab seit Ende Januar kein Milestone mehr sondern ein Desire mit 1Ghz Snapdrogon.
    Interessehalber hab ich mein Testprogramm (die Float-Version) auf dem Desire jetzt mal laufen lassen.
    Ergebnis: ~ 2100 ms Laufzeit
    Nochmal der Vergleich zum Milestone: 4066 ms

    Also fast doppelt so schnell. Nicht schlecht.
    Wenn es Android 2.2 auf dem Desire gibt werd ich wahrscheinlich nochmal nen Test machen um zu sehen was der JIT-Compiler noch so rausholt.

    Nachtrag: Hab jetzt die double-Variante auf dem Desire laufen lassen. Die braucht nur 1850 ms (!) ist also schneller als die Float-Variante.

    Gruß
    Jarny
     
    Zuletzt bearbeitet: 16.05.2010

Diese Seite empfehlen