Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Codeoptimierung in "C" (Matheoperatoren)

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 < [ 2 ]
000
20.10.2006, 14:11 Uhr
Archimedes



Hi,

ich bin gerade an einer komplexen Aufgabe, welche viele mathematischen Operationen benötigt. Dies ist leider recht schlimm, da meine Anwendung in einem Echtzeitsystem stattfindet und im "worst case" die Zeitvorgaben (leider) noch nicht einhalten kann.

Mit Hilfe von einem Performance Analyser habe ich herausgefunden, dass folgende Zeilen einen Grossteil der Verantwortung für die langsame Geschwindigkeit haben.

// Hier ein kleiner Ausschnitt

C++:
long i, j;
long x, y;
long r;
long MAXRADIUS;
float fValue;
float fBetrag;
float iHeight;

for(...) {
  for(...) {
    for(...) {
      ...
      r = (long)( sqrt((float)(( j - x )*( j - x )) + (float)(( i - y )*( i - y ))));
      plImg[((long)( x / 2 ) * iHeight + (long)( y / 2 )) * MAXRADIUS + (long)( r / 2 )]+= (long)(( fBetrag * fValue ));
      ...
    }
  }
}



Hat jemand eine Idee, wie ich diese Ausdrücke schneller machen kann ?
Möglicherweise gibt es Bibliotheken, die optimiert auf mathematischen Funktionen sind ?
Kann man die Formeln evtl. umstellen, damit der Compiler besseren "C"-Code erzeugt ?

Vielen Dank, Grüsse,
Archi


PS: Ich arbeite auf einem Intel 3 GHz

Dieser Post wurde am 20.10.2006 um 14:13 Uhr von Archimedes editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
20.10.2006, 14:56 Uhr
RedEagle



Der inhalt der for-schleifen währe nicht uninteressant.
Welche variablen werden dort verändert??

evtl kann man so abschnitte wir
(fBetrag*fValue) vor den Schleifen, bzw zumindest vor einer schleife verschieben, damit das nicht 1000 mal berechnet werden muss für das selbe ergebniss
--
MFG RedEagle
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
20.10.2006, 15:20 Uhr
ao

(Operator)


Viel Potential sehe ich da nicht, aber etwas schon

1. Doppelte Berechnungen vermeiden ( (j-x) * (j-x) ), stattdessen Zwischenergebnisse speichern:

C++:
int f1 = j - x;
int f2 = i - y;
r = (long) sqrt (f1 * f1 + f2 * f2);



2. Die float-Casts sind unnötig.
Erstens muss man nicht die Produkte einzeln casten, um sie dann zu addieren. Addiere zuerst und caste dann, dadurch entfällt ein Cast.
Zweitens erwartet sqrt ein double-Argument, und der Compiler wandelt long nach double automatisch bei Bedarf. Der erzwungene float-Cast ist also ein Umweg.

Der Rück-Cast auf long bei Zuweisung der Wurzel an r erfolgt ebenfalls automatisch, allerdings kann es sein, dass der Compiler eine Warnung wirft, wenn du ihn weglässt ("Conversion loses precision" oder so). Schreib ihn hin, er schadet nicht.

3. Die Indexberechnung kann man umstellen:

C++:
index = (x * iHeight + y * MAXRADIUS + r) / 2;


Dadurch hast du nur eine Division anstatt drei. Aber aufpassen, ob dadurch ein Überlauf entstehen kann!
Auch hier sind sämtliche Casts bis auf den letzten ( (long)(( fBetrag * fValue )) ) unnötig, weil nur Ganzzahlen beteiligt sind und daher komplett im long-Format gerechnet wird. Oder ist MAXRADIUS eine Fließkomma-Konstante?

4. Was sich richtig lohnen könnte, ist ein kritischer Blick auf die drei geschachtelten Schleifen. Zeig doch mal die Schleifenköpfe und welche Schleife über welche Schleifenvariable läuft und so. Eventuell kann man einen Teilausdruck weiter nach außen ziehen, so dass der nicht in jedem innersten Durchlauf neu berechnet werden muss.

ao
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
20.10.2006, 16:00 Uhr
Archimedes



Hi,

vielen Dank, für die schnellen tollen Antworten !

@RedEagle
Das stimmt. Ich habe die for-Schleifen noch einmal angeschaut und in meinem Leichtsinn wirklich etwas Blödsinn gemacht ! Die Frage ist, ob der Compiler dort meine Faulheit schon behoben hat, oder nicht ! Ich werd es auf jeden Fall testen !

C++:
for( i = nStart_y; i < nHeight + nStart_y; i++ )
for( j = nStart_x; j < nWidth + nStart_x; j++ )
for( y = y_min; y < y_max; y = y + 2 )



Die Addition in der Schleife: "nHeight + nStart_y" und "nWidth + nStart_x" werde ich jweils eine Ebene globaler 1x ausrechnen. Die Werte werden nicht weiter veraendert. Bei "y = y + 2" bin ich mir nicht so sicher, ob ich das besser/schneller schreiben kann.

@ao
Wow, das sind echt tolle Anregungen.

zu 1) Ich bin mir nicht sicher, ob es schneller ist, dem Wert extra einen Speicherplatz zu geben und darauf zuzugreifen. Wenn im Cache viel in der Adressierung gesprungen wird, wirds langsamer ! Ich werds mal ausprobieren

zu 2) Die floats sehe ich ein. Danke fuer den Tipp. Die longs brauche ich jedoch. Es ist an jeder Stelle immer wichtig eine abgerundete "normale Zahl" zu bekommen. Ist aus der Semantik heraus wichtig. Das zu erklaeren wuerde den Umfang hier sprengen.

zu 3) Das 1/2 heraus zu ziehen hab ich glatt übersehen. Bei Formeln schreibt man sich gerne alles aufs Papier... bei simplen Indexen macht man das leider nicht und verliert so schnell Geschwindigkeit.
Ich schlage bei diesem Punkt selber noch vor, anstatt der Division 1/2 eine Multiplikation von 0.5 zu machen. Divisionen werden von der ALU weit langsamer ausgefuehrt.

zu 4) Oben stehen die Schleifen. Vielleicht faellt dir noch was ein.


Ich werde meine Ergebnisse Anfang naechster Woche hier niederschreiben.
Danke nochmals vielmals.
Gruesse,
Archi
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
20.10.2006, 16:10 Uhr
ao

(Operator)



Zitat von Archimedes:
zu 1) Ich bin mir nicht sicher, ob es schneller ist, dem Wert extra einen Speicherplatz zu geben und darauf zuzugreifen. Wenn im Cache viel in der Adressierung gesprungen wird, wirds langsamer ! Ich werds mal ausprobieren

Stimmt, kann so oder so sein. Ausprobieren macht schlau.

Zitat:
zu 3) Ich schlage bei diesem Punkt selber noch vor, anstatt der Division 1/2 eine Multiplikation von 0.5 zu machen. Divisionen werden von der ALU weit langsamer ausgefuehrt.

Vorsicht, Äpfel-Birnen-Vergleich. Das ist eine Integer-Division. Multiplikation mit 0.5 bedeutet Wandlung nach double, dann Rechnung und Rückwandlung des Ergebnisses nach int. Auch hier: ausprobieren.

Zitat:
zu 4) Oben stehen die Schleifen. Vielleicht faellt dir noch was ein.

Ja, der Ausdruck
C++:
(long)(( fBetrag * fValue ))
ist in allen drei Schleifen konstant. Also vorher einmal berechnen und dann immer nur verwenden.

Zitat:
Ich werde meine Ergebnisse Anfang naechster Woche hier niederschreiben.

Tu das, bin gespannt.

ao
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
20.10.2006, 18:37 Uhr
0xdeadbeef
Gott
(Operator)


Zunächst mal schreib ich der Übersichtlichkeit halber mal den Code ohne die unleserlichen casts auf - das kann ja sonst nachher kein Mensch debuggen.

C++:
for(...) {
  for(...) {
    for(...) {
      ...
      r = sqrt(( j - x )*( j - x ) + ( i - y )*( i - y ));
      plImg[(x / 2 * iHeight + y / 2) * MAXRADIUS + r / 2] += fBetrag * fValue;
      ...
    }
  }
}



Die Basisrechenoperationen wird der Compiler schon optimieren können, zumindest, wenn er einigermaßen neu ist - ich bezweifle, dass da wirklich Geschwindigkeit verloren geht. Worüber ich mir Gedanken machen würde, wäre eher der Algorithmus selbst - kannst du vielleicht ein paar Schleifendurchläufe einsparen, oder ein paar kostenintensivere Rechenoperationen wie z.B. die sqrt. Vor allem bei den Schleifendurchläufen würd ich mal sehr genau hinkucken, ich kann mir gut vorstellen, dass du da nen ganzen Haufen Pixel doppelt beschreibst.

Ansonsten, was Mikrooptimierungen angeht,

C++:
plImg[((x * iHeight + y)  * MAXRADIUS + r) / 2 ] += fBetrag + fValue;


Wenn du i und j nur alls j - x bzw. i - y verwendest, bietet sich unter Umständen etwas wie

C++:
for(i = nStart_y - y; i < nHeight + nStart_y - y; ++i)
for(j = nStart_x - x; j < nWidth + nStart_x - x; ++j)


an, um dann i und j direkt zu verwenden - wobei das aber natürlich davon abhängt, ob x und y in der Schleife geändert werden.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 20.10.2006 um 18:49 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
23.10.2006, 13:51 Uhr
Archimedes



Soooo, ich hab mal ein bischen experimentiert und ein paar interessante Sachen herausgefunden:


C++:
for( i = iStart_y; i < iHeight + iStart_y; i++ )

VS

maxHeight = iHeight + iStart_y;
for( i = iStart_y; i < maxHeight; i++ )


bringt keine Verbesserung. Der Plusoperator ist entweder zu einfach, oder durch initialisieren einer neuen Variable und deren Zugriff verliere ich Performance !

Interessanter wirds hier:

C++:
plHoughCnt[((long)( x / 2 ) * iHoughHeight + (long)( y / 2 )) * CVC_MAXHOUGHRADIUS + (long)( r / 2 )]++;

VS

long coord = (( x >> 1 ) * iHoughHeight + ( y >> 1 )) * CVC_MAXHOUGHRADIUS + ( r >> 1 );
plHoughCnt[coord]++;



Da ich coord an 2 Stellen benötige, verwende ich eine neue Variable. Die Division durch 2 heraus zu ziehen ist nicht möglich. Meine Semantik lässt das nicht zu (--> x,y,r MÜSSEN durch den Cast abgerundet werden) .
Die Division durch 2 habe ich nun durch den Schiebeoperator ersetzt, welcher mir ein Bit zur Seite schiebt (Binärzahlen...). Dieser Trick reicht aus, um diese Zeile 3x so schnell und weniger rechenintensiv zu machen.

Wie einige sicherlich schon gemerkt haben, handelt es sich bei meinem Projekt um Bildverarbeitung.

Dieser Post wurde am 23.10.2006 um 13:53 Uhr von Archimedes editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
23.10.2006, 14:02 Uhr
Guybrush Threepwood
Gefürchteter Pirat
(Operator)



Zitat von Archimedes:

bringt keine Verbesserung. Der Plusoperator ist entweder zu einfach, oder durch initialisieren einer neuen Variable und deren Zugriff verliere ich Performance !


Wahrscheinlicher ist es das dein Compiler genau diese Optimierung schon automatisch vorgenommen hat und die Addition aus dem Schleifenkopf rausgezogen hat.

Zitat von Archimedes:

Die Division durch 2 habe ich nun durch den Schiebeoperator ersetzt, welcher mir ein Bit zur Seite schiebt (Binärzahlen...). Dieser Trick reicht aus, um diese Zeile 3x so schnell und weniger rechenintensiv zu machen.


Das ist normal. Eine Bitverschiebung geht immer sehr viel schneller als eine Multiplikation bzw. Division
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
008
23.10.2006, 15:52 Uhr
ao

(Operator)



Zitat von Guybrush Threepwood:
Das ist normal. Eine Bitverschiebung geht immer sehr viel schneller als eine Multiplikation bzw. Division

Was mich aber wundert, ist, dass der Compiler bzw. der Optimizer das nicht zu wissen scheint. Ich hab schon vor Jahren mit Compilern gearbeitet, die Divisionen durch Zweierpotenzen erkannten und durch einen Bitschieber ersetzten, ganz ohne mein Zutun.

ao
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
009
23.10.2006, 16:22 Uhr
Guybrush Threepwood
Gefürchteter Pirat
(Operator)


Hmm ja stimmt, dazu wäre dann interessant zu wissen welchen Compiler er benutzt.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 < [ 2 ]     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: