[C++] Schnellster Test POINT ggn RECT

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Hi,

Mag im Grunde bedeutungslos sein, aber des Spaßes halber: was ist der schnellstmögliche Test eines ::POINT gegen ein ::RECT?

Bei der ursprünglichen Version braucht man ja vier Vergleiche:

Code: Alles auswählen

bool PointIsInRect(
	::POINT const &	p_Point,
	::RECT const &	p_Rectangle
) throw() {
	return (p_Point.x >= p_Rectangle.left) && (p_Point.y >= p_Rectangle.top)
		 && (p_Point.x < p_Rectangle.right) && (p_Point.y < p_Rectangle.bottom);
}
Nun habe ich mir gedacht: Die Koordinaten sind eh immer in den unteren Bits, also kann man doch den Koordinatenursprung abziehen, zu unsigned long konvertieren und die Sache damit auf zwei Vergleiche reduzieren:

Code: Alles auswählen

bool PointIsInRect_fast(
	::POINT const &	p_Point,
	::RECT const &	p_Rectangle
) throw() {
	return (unsigned long(p_Point.x - p_Rectangle.left) < unsigned long(p_Rectangle.right  - p_Rectangle.left))
		&& (unsigned long(p_Point.y - p_Rectangle.top) < unsigned long(p_Rectangle.bottom - p_Rectangle.top));
}
Allerdings ist dieser Code nur fünf Prozent schneller als der Ursprüngliche, läuft außerdem auf gleich viele Befehle hinaus. Das Problem ist scheinbar, dass der Compiler immernoch jmps verbaut, um die beiden Vergleiche abhängig voneinander durchzuführen (schlägt der Erste fehl, wird der Zweite garnicht erst ausgeführt) … ich habe versucht, die Vergleiche durch lokale Variablen zu erzwingen, aber die werden wegoptimiert (VC 2008 SP1). Wie also kriegt man das noch schneller?

Gruß, Ky

Edit: Wenn man das logische && gegen ein bitweises & tauscht, führt der Prozessor garantiert beide Vergleiche durch und die Sache geht doppelt so schnell über die Bühne. Ist bitweises Verknüpfen logischer Aussagen legitim?
Zuletzt geändert von Krishty am 27.11.2009, 16:15, insgesamt 2-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Gegenfrage...warum sollte es nicht legitim sein? Bitweise Operatoren sind fuer Integer-Typen definiert und bool wird entweder zu 0 oder 1 konvertiert, wenn man es einem Integer zuweist.

Code: Alles auswählen

        return !((unsigned long(p_Point.x - p_Rectangle.left) - unsigned long(p_Rectangle.right  - p_Rectangle.left))
                | (unsigned long(p_Point.y - p_Rectangle.top) - unsigned long(p_Rectangle.bottom - p_Rectangle.top)) & 0x80000000);
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Es ist sogar vorgeschrieben, das der zweite Vergleich nicht durchgefuehrt werden darf, da C++ ueber short circuit evaluation verfuegt. Werde dir erstmal ueber solche Grundsaetze klar, bevor du dich mit solchen "Optimierungen" beschaeftigst. f'`8k

[ ] Autocogito


Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Jörg hat geschrieben:Gegenfrage...warum sollte es nicht legitim sein? Bitweise Operatoren sind fuer Integer-Typen definiert und bool wird entweder zu 0 oder 1 konvertiert, wenn man es einem Integer zuweist.
Ich hatte im Verdacht, für true würde ein beliebiger non-zero-Wert genommen und da könnte es dann krachen.

Danke für den Code, nochmal 10% schneller :)
Edit: Ähm, so richtig kann das nicht klappen: Das bitweise AND hat Priorität über dem OR.
Ich habe schnell das hier gebastelt:

Code: Alles auswählen

return !(
	 (ULong(p_Point.x - p_Rectangle.left) // Negativ, falls links
	| ULong(p_Point.y - p_Rectangle.top) // Negativ, falls über
	| ULong((p_Rect.right - p_Rect.left) - 1 - p_Point.x) // Negativ, falls rechts (rechter und unterer Rand exklusiv!)
	| ULong((p_Rect.bottom - p_Rect.top) - 1 - p_Point.y)) // Negativ, falls unter
& 0x80000000);
Aber das ist wieder ein paar Prozent langsamer :/
TGGC hat geschrieben:Es ist sogar vorgeschrieben, das der zweite Vergleich nicht durchgefuehrt werden darf, da C++ ueber short circuit evaluation verfuegt. Werde dir erstmal ueber solche Grundsaetze klar, bevor du dich mit solchen "Optimierungen" beschaeftigst.
Das weiß ich doch. Ich hätte allerdings erwartet, dass der Optimierer die Regel dem Pipelining opfert, da dort ausschließlich native Datentypen verglichen werden und die Ausführung damit keine Auswirkungen auf den restlichen Programmfluss hat, außer eben, dass er mit ihr schneller und kürzer ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Sorry, ich hatte eine Klammer vergessen....

Eigentlich sollte es folgendes sein :

Code: Alles auswählen

        return (signed long)(((unsigned long(p_Point.x - p_Rectangle.left) - unsigned long(p_Rectangle.right  - p_Rectangle.left))
                | (unsigned long(p_Point.y - p_Rectangle.top) - unsigned long(p_Rectangle.bottom - p_Rectangle.top)))) < 0;
Ueber short-circuit muessen wir uns bei bitweisen Ops ja gottseidank keine Gedanken machen ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Nein, es funktioniert einfach nicht (meins aber auch nicht ;) ): Nehmen wir an, das Rect geht von 1,1 bis 4,4 und der Punkt liegt bei 0,0:

return (signed long)(((unsigned long(0 - 1) - unsigned long(4 - 1)) | (unsigned long(0 - 1) - unsigned long(4 - 1)))) < 0;
return (signed long)(((0xFFFFFFFF - 0x3) | (0xFFFFFFFF - 0x3))) < 0;
return (signed long)(0xFFFFFFFC | 0xFFFFFFFC) < 0;
return -4 < 0;
return true;

Obwohl der Punkt außerhalb liegt. Vertauscht man zwei Operanden der äußeren Subtraktionen, funktioniert es zumindest, wenn der Punkt größere Koordinaten als das Rect hat. Ich verstehe auch nicht, wie du ohne Vergleich in zwei Schritten gegen vier Grenzen testen willst?

Ich bin mittlerweile bei

Code: Alles auswählen

return !(
         (ULong(p_Point.x - p_Rectangle.left) // Negativ, falls links
        | ULong(p_Point.y - p_Rectangle.top) // Negativ, falls über
        | ULong(p_Rect.right - 1 - p_Point.x) // Negativ, falls rechts (rechter und unterer Rand exklusiv!)
        | ULong(p_Rect.bottom - 1 - p_Point.y)) // Negativ, falls unter
& 0x80000000);
Das funktioniert relativ sicher, ist aber einen Tick langsamer als die Version mit zwei Vergleichen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Na hui, ich hatte mich auf deinen 2. Code verlassen :) Zurueck ans Reissbrett...
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Achso – mein zweiter Code funktioniert, er testet aber gegen die Breite des Rechtecks und macht sich zunutze, dass alles links und über dem Rechteck zu einer großen positiven Zahl unterläuft. Das kann man nicht so einfach durch eine Subtraktion ersetzen.

Noch mehr „hui“ ist, dass die derzeit schnellste Version diese hier ist:

Code: Alles auswählen

return (p_Point.x >= p_Rectangle.left ) & (p_Point.y >= p_Rectangle.top   )
    & (p_Point.x <  p_Rectangle.right) & (p_Point.y <  p_Rectangle.bottom);
3% schneller als mein zweiter Code. Und viel lesbarer. (14 Befehle, keine Sprünge – mit logischem AND waren es fast 30 Befehle und drei Sprünge.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Ja, das hab ich eben bemerkt ... d.h. man muss die Breite mit einbeziehen.
Dann kommt aber folgendes auch auf 14-15 Befehle:

Code: Alles auswählen

const unsigned long dx = (unsigned long(p_Rectangle.right  - p_Rectangle.left - p_Point.x) | unsigned long(p_Point.x - p_Rectangle.left));
const unsigned long dy = (unsigned long(p_Rectangle.bottom  - p_Rectangle.top - p_Point.y) | unsigned long(p_Point.y - p_Rectangle.top));
return ((signed long)(dx | dy)) > 0;
Edit: Und wenn Du ein Rect mit top-left-height-width hast, dann sparste nochmal 'dicke' ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Ist ja fast gleich meinem Code, sogar mit gleichen Fehlern ;)
Jörg hat geschrieben:Edit: Und wenn Du ein Rect mit top-left-height-width hast, dann sparste nochmal 'dicke' ;)
Nein, das muss garnicht sein: unsigned long(p_Rectangle.right - p_Rectangle.left - p_Point.x) kannst du durch unsigned long(p_Rectangle.right - p_Point.x) ersetzen, weil der Punkt und das Rechteck ja im selben Koordinatensystem liegen (den gleichen Fehler hatte ich auch in meinem dritten Code). Dafür musst du aber 1 abziehen, weil RECT::right und RECT::bottom nicht mehr Teil des Rechtecks sind.

Werde deinen Code mal benchen, dauert nur etwas, habe hier ein kleines Speicherproblem.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Ja, du darfst nun ruhig sagen, dass ich doof bin :) Freilich nimmste die raus, dann bist du bei der sign-bit-logikvariante deines Codes.
Ist es nicht viel spannender zu fragen, warum der Compiler das nicht aus allen moeglichen (korrekten ;) natuerlich) Code-Varianten gebacken bekommt?
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Jörg hat geschrieben:Ja, du darfst nun ruhig sagen, dass ich doof bin :)
Nee, Glashaus und so ;)
Jörg hat geschrieben:Ist es nicht viel spannender zu fragen, warum der Compiler das nicht aus allen moeglichen (korrekten ;) natuerlich) Code-Varianten gebacken bekommt?
Ich frage mich ja schon, warum er mit logischem AND Sprünge produziert, wo es doch keinerlei Side-Effects gibt … und naja, so eine Sache, wo mich der Optimizer mit else-ifs und Sprungtabellen enttäuscht hat, gab es ja auch schon. Der ist wohl einfach nicht so gut, wie er sein sollte.

Benchmark-Time:

Dein Code (mit Korrektur) (0.246673s, 0.250028s, 0.248889s):

Code: Alles auswählen

const unsigned long dx = (unsigned long(p_Rectangle.right - 1 - p_Point.x) | unsigned long(p_Point.x - p_Rectangle.left));
const unsigned long dy = (unsigned long(p_Rectangle.bottom - 1 - p_Point.y) | unsigned long(p_Point.y - p_Rectangle.top));
return ((signed long)(dx | dy)) > 0;
Mein zweiter Code (0.245241s, 0.242956s, 0.245025s):

Code: Alles auswählen

return (unsigned long(p_Point.x - p_Rectangle.left) < unsigned long(p_Rectangle.right  - p_Rectangle.left))
	 & (unsigned long(p_Point.y - p_Rectangle.top ) < unsigned long(p_Rectangle.bottom - p_Rectangle.top ));
Lesbare Version (0.234394s, 0.23476s, 0.250995s):

Code: Alles auswählen

return (p_Point.x >= p_Rectangle.left ) & (p_Point.y >= p_Rectangle.top   )
	 & (p_Point.x <  p_Rectangle.right) & (p_Point.y <  p_Rectangle.bottom);
Grundlage waren drei Läufe von je 50 mio zufälligen ::RECTs und ::POINTs, in Schleifen à vier Ausführungen, alles geinlined unter VC 2008 SP1. Im letzten Lauf habe ich die Reihenfolge der geändert und prompt war die bis dahin schnellste Version die Langsamste … ich würde also sagen, dass sie unter realen Bedingungen alle gleich schnell sind … (übrigens alle rund zweieinhalb Mal so schnell wie das Original mit logischem AND)

Ich denke: wenn jetzt keine genialen Geistesblitze mehr kommen, ist das die Grenze des Machbaren. Ich werde die lesbare Version nehmen, die ist am elegantesten. Danke auf jeden Fall für die Hilfe :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Bereits der Speicherzugriff koennte einen Sideeffekt sein. Ausserdem weiss ich nicht, ob zufaellige points und rects wirklich ein realistisches Szenario sind. Zudem hast du nur die Geschwindigkeit auf einer einzigen CPU getestet.

Ausserdem wird man bevor man den Rect Test aufruft sowas mit Space Partitioning angehen oder einem sweep Algo. Danach ist dann evtl. gar kein kompletter Test mehr noetig. f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Der Vollstaendigkeit halber:

x86-Logik-Schule:
4x laden
4x vergleichen
4x vergleich-ergebnis speichern (ja, denn das muss ja irgendwie aus den Flags raus)
3 ergebnis kombinieren
--
15

Der Compiler erzwingt 2x Register-Loeschen vor setcc Instruktionen, was man vielleicht durch (unsigned char)-Formulierungen verhindern koennte, so dass man am Ende etwa 17 Instruktionen erhaelt.

Sign-Dao fuer x86:
4 laden
4 subtrahieren
2 kombinieren
1 invertieren (*kann durch Vorbereitung der Daten gespart werden)
1 kombinieren
--
12 (*11)

Wenn man es genau in dieser Reihenfolge aufschreibt, geht das fast 1:1 durch den Compiler, nur am Ende, wenn es um das casten
nach bool geht, will er noch ein Register-Löschen nebst setl verwenden, was zu guter Letzt zu 12+1 = 13 Instruktionen fuehrt. Dabei wird bei dieser Variante 2er-Komplement-Darstellung angenommen.

Die ersten 10 Schritte lassen sicherlich Raum fuer eine SSE2-Variante.

Code: Alles auswählen

bool	PtInRect(const ::RECT &r, const ::POINT &p) {
	unsigned long a,b,c,d;
	a = p.x - r.right;
	b = p.y - r.bottom;
	c = r.left - p.x;
	d = r.top - p.y;

	a |= b;
	c |= d;

	a |= ~c;
	return (signed long)a < 0;
}
Die Behandlung der Begrenzung kannst Du Dir bei Bedarf anpassen, der Trcik ist einfach, alles per Subtraktion zu erledigen und den kritischen Fall durch logisches negieren anstelle von Einzeladditionen zu erschlagen.
Ob es schneller ist? Keine Ahnung, aber wenn Du es inlinst und haeufig verwendest, kann Instruction-Count ja auch eine Rolle spielen. Bei unbekannter Zielplatform wuerde ich die klassische Variante bevorzugen, aber bei fester Architektur sollte man sowas nicht aus den Augen verlieren. Wofuer brauchst Du es eigentlich? GUI-Abfrage ? ;)
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Gerade bei einer GUI muss ja sogar ein hierachisches System eingehalten werden, weil ich z.b. nicht in Fenster klicken kann, das unter einem anderem liegt. Oder ein Button, der per Scrollbar (teilweise) aus dem Bild gescrollt wurde darf nur klickbar sein, wenn die Maus auch im Parent ist. Darum ist dieser Test sicher nicht der Bottleneck sondern die ganze Verwaltung drumherum.

Was ist uebrigens der Sinn von den casts nach unsigned? f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Bitweise ops und signed-typen sind der Grund.
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Bitweise Operatoren veraendern signed Werte auf genau die gleiche Weise wie unsigned Werte - denn sie schauen sich ja nur die Bits an, die Interpretation ist ihnen egal. f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Richtig, ob ein Wert signed oder unsigned ist, ist reine Interpretationssache und der CPU egal. Ist ja gerade das Bequeme am Zweierkomplement.
TGGC hat geschrieben:Bereits der Speicherzugriff koennte einen Sideeffekt sein.
Ja, das stimmt. Es wurde aber auch nicht optimiert, wenn die Parameter per-value übergeben wurden oder wenn die Funktion inline auf Parameter angewandt wurde, die auf dem Stack liegen (also in Fällen, wo der Speicher eh zwei oder drei Instruktionen vorher benutzt wurde).
TGGC hat geschrieben:Außerdem weiss ich nicht, ob zufaellige points und rects wirklich ein realistisches Szenario sind.
Wo soll denn der Unterschied zu realen Situationen sein? Die Ausführungsgeschwindigkeit eines Vergleichs müsste unabhängig vom Wert der Operanden sein, die Sprungvorhersage müsste bei jeder Ausführung gleich gut funktionieren. (Ich habe die Ergebnisse nicht im Programmfluss verwendet sondern in ein Array geschrieben und gegen sichere Ergebnisse verglichen.)
TGGC hat geschrieben:Zudem hast du nur die Geschwindigkeit auf einer einzigen CPU getestet.
Ja, ich habe hier leider nur Core 2s :(
TGGC hat geschrieben:Ausserdem wird man bevor man den Rect Test aufruft sowas mit Space Partitioning angehen oder einem sweep Algo. Danach ist dann evtl. gar kein kompletter Test mehr noetig.
TGGC hat geschrieben:Gerade bei einer GUI muss ja sogar ein hierachisches System eingehalten werden, weil ich z.b. nicht in Fenster klicken kann, das unter einem anderem liegt. Oder ein Button, der per Scrollbar (teilweise) aus dem Bild gescrollt wurde darf nur klickbar sein, wenn die Maus auch im Parent ist. Darum ist dieser Test sicher nicht der Bottleneck sondern die ganze Verwaltung drumherum.
Jörg hat geschrieben:Wofuer brauchst Du es eigentlich? GUI-Abfrage ? ;)
Ich prüfe auf einem Multi-Monitor-System, welche Fenster von welcher GPU gerendert werden müssen. Ein Bottleneck ist es definitiv nicht, meistens ist der erste Monitor ein Treffer, usw. Beim Debugging fiel mir aber die schiere Menge an Sprüngen auf, die der Code produziert hat, und da wollte ich wissen, wie es optimal vonstatten geht (Spaß an der Freude also). Bei GUIs u.Ä. wäre ein hierarchisches System auf jeden Fall die beste Lösung, aber hier geht es nicht so ohne Weiteres.
Jörg hat geschrieben:Der Vollstaendigkeit halber: [...] Der Compiler erzwingt 2x Register-Loeschen vor setcc Instruktionen, was man vielleicht durch (unsigned char)-Formulierungen verhindern koennte, so dass man am Ende etwa 17 Instruktionen erhaelt.
Ich bin mir sicher, dass es weniger waren, das kommt aber wohl daher, dass ich die Funktionen viermal hintereinander aufgerufen habe und damit das Laden der Quelladressen und das Löschen der Register aus meiner Zählung rausfiel ... wenn ich heute nachmittag an einen Compiler komme, zähle ich nochmal im Einzelaufruf nach und teste auch deinen neuen Code.
Jörg hat geschrieben:Die ersten 10 Schritte lassen sicherlich Raum fuer eine SSE2-Variante.
Habe ich mir auch schon überlegt, ein RECT wäre optimal für ein SSE-Register. Dann würde aus vier Loads vielleicht nurnoch eins.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Jörg »

Krishty hat geschrieben:Richtig, ob ein Wert signed oder unsigned ist, ist reine Interpretationssache und der CPU egal. Ist ja gerade das Bequeme am Zweierkomplement.
Alte Routine....2er-Komplement ist nicht vom C-Standard gefordert. D.h. die Auswirkungen von bitlogik auf vorzeichenbehaftete Typen ist nicht klar.
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Dann nehme ich das sofort zurück :) Solche Fakten sind eigentlich genau der Grund, warum ich hier immer für alles nachfrage.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Krishty hat geschrieben:Wo soll denn der Unterschied zu realen Situationen sein? Die Ausführungsgeschwindigkeit eines Vergleichs müsste unabhängig vom Wert der Operanden sein, die Sprungvorhersage müsste bei jeder Ausführung gleich gut funktionieren.
Du sagst ja selbst "meistens ist der erste Monitor ein Treffer" - also kein zufaelliges Ergebnis. Anderes Beispiel, wenn ich einen 2D Shooter machen und teste, welche Geschosse Gegner treffen, dann ist das Ergebnis meist kein Treffer. Denn selbst ein Geschoss das trifft, wird vorher schon meinetwegen 50 Frames lang "keine Kollision" liefern. Auch kein zufaelliges Ergebnis. f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

TGGC hat geschrieben:Du sagst ja selbst "meistens ist der erste Monitor ein Treffer" - also kein zufaelliges Ergebnis. Anderes Beispiel, wenn ich einen 2D Shooter machen und teste, welche Geschosse Gegner treffen, dann ist das Ergebnis meist kein Treffer. Denn selbst ein Geschoss das trifft, wird vorher schon meinetwegen 50 Frames lang "keine Kollision" liefern. Auch kein zufaelliges Ergebnis.
Schon, aber die Algorithmen hier haben ja alle die gleiche Laufzeit, weil es keine Sprünge mehr gibt. Der Geschwindigkeitsschub kommt doch gerade daher, dass die Befehle dieselben bleiben, egal, ob der Vergleich wahr oder falsch ergibt. Dass ich überhaupt Variationen in die Testdaten gebracht habe dient nur dem Zweck, dass ich die Ergebnisse gegen die Referenz prüfen kann und der Compiler nicht den kompletten Test wegoptimiert. Insofern muss es ziemlich repräsentativ sein.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Krishty hat geschrieben:Schon, aber die Algorithmen hier haben ja alle die gleiche Laufzeit, weil es keine Sprünge mehr gibt.
Aber die mit denen du die Laufzeit vergleichst, schon. Ich sehe kein Argument warum ein Algorithmus ohne Spruenge grundsaetzlich schneller sein soll, ein Algorithmus mit Sprung koennte als ersten Befehl einen Sprung haben, der in 99% aller Faelle den Algorithmus beendet -> schneller im avg case. f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Helmut »

TGGC hat geschrieben:Ich sehe kein Argument warum ein Algorithmus ohne Spruenge grundsaetzlich schneller sein soll...
Das hat ja auch keiner behauptet, aber manchmal kann es eben passieren. Wie in diesem Fall. Ist ja auch klar, die Sprungvorhersage funktioniert besser, der Instructioncount wird ev. kleiner und Sprünge brauchen nun mal auch Zeit. Und auch wenn Speicherzugriffe Nebeneffekte haben können, darf der Compiler diese durchaus wegoptimieren. Zumindest solange kein volatile im Spiel ist. Von daher hätte ich auch erwartet, dass der Optimierer von MS das hinkriegt.

Ciao
Helmut (, der PtInRect benutzt)
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Genau das wurde indirekt behauptet: Dieser Code waere der schnellste bei zufaelligen Daten. Diese zufaelligen Daten haetten keinen Unterschied zu realen Daten, da der Code keine Spruenge benutzt. Daraus folgte, dieser Code ist schneller als ein Code mit Spruengen, weil er keine Spruenge hat. f'`8k

Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

::PtInRect(): 0.987226s
Lesbare Version: 0.32199s
Jörgs Version: 0.375697s

Soo, der Reihe nach: Danke, Helmut, dass du mich auf ::PtInRect() aufmerksam gemacht hast. Irgendwo musste ja ein Makro oder eine Funktion existieren, das/die diese Aufgabe erledigt – der kryptische Name hatte mir die Suche aber vermasselt …
… mit der Funktion gibt es drei Probleme: Zum einen verrät mir die Disassembly, dass sie die logischen ANDs und damit drei Sprünge benutzt. Zum Anderen weigert sich VC, sie zu inlinen. Zu guter Letzt benutzt sie BOOL statt bool und VC wirft deswegen schon im Voraus eine Performance-Warnung.

Die Disassembly meiner Funktion (inline, für einen einzelnen Schleifendurchgang):

Code: Alles auswählen

mov         eax,dword ptr [ebp+54h] 
mov         edi,dword ptr [ebp+64h] 
xor         esi,esi 
add         eax,4 
mov         dword ptr [ebp+54h],eax 
lea         esp,[esp] 
mov         edx,dword ptr [edi+4] 
cmp         edx,dword ptr [eax+8] 
mov         ecx,dword ptr [edi] 
setl        dl   
cmp         ecx,dword ptr [eax-4] 
setge       bl   
and         dl,bl 
cmp         ecx,dword ptr [eax+4] 
setl        cl   
and         dl,cl 
mov         ecx,dword ptr [edi+4] 
cmp         ecx,dword ptr [eax] 
setge       cl   
and         dl,cl 
mov         ecx,dword ptr [ebp+50h] 
mov         byte ptr [esi+ecx],dl 
Die Kürze gestern lag also tatsächlich daran, dass ich die Schleife zu Viererblöcken abgerollt hatte.

Jörg, was deine neue Funktion angeht (ich habe wieder die zwei Dekrementierungen ergänzt), das hier ist ihre Disassembly (für einen einzelnen Schleifendurchgang):

Code: Alles auswählen

mov         eax,dword ptr [ebp+64h] 
mov         ecx,dword ptr [eax] 
mov         eax,dword ptr [eax+4] 
mov         esi,dword ptr [edx] 
sub         esi,eax 
mov         edi,eax 
mov         eax,dword ptr [edx-4] 
sub         edi,dword ptr [edx+8] 
sub         eax,ecx 
sub         ecx,dword ptr [edx+4] 
dec         eax  
dec         esi  
or          eax,esi 
not         eax  
or          ecx,eax 
or          ecx,edi 
mov         ecx,dword ptr [ebp+50h] 
setl        al   
add         dword ptr [ebp+64h],8 
mov         byte ptr [ebx+ecx],al 
Sie ist zwei Anweisungen kürzer, aber trotzdem langsamer (auch dann noch, wenn ich die Ausführungsreihenfolge mit der lesbaren Version vertausche).

@Sprünge: Selbst, wenn der erste Sprung die restlichen drei Vergleiche überspringen kann ist die Performance immernoch schlechter, weil der dann auszuführende Code nicht bereit in der Pipeline liegt. Die Sprungvorhersage einer CPU kann noch so effizient arbeiten, wenn der Sprung (wie hier) auf einer direkt zuvor ausgeführten Vergleichsoperation beruht, kann man das garnicht out-of-order genug ausführen, um nicht zu verlieren. Natürlichen können Sprünge große Brocken Ausführungszeit abfangen und ich will nicht verallgemeinern, dass sprungfreier Code schneller wäre – aber diese Mini-Funktion hier wird nunmal mehr als doppelt so schnell (und, wie Helmut richtig bemerkte, ein wenig kleiner), sobald man ihre Sprünge weglässt. Kannst das ja gerne testen, ich bitte dich sogar darum, schließlich haben wir hier immernoch keine Ergebnisse von anderen CPUs als meinem Core 2 Quad.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Wenn der Sprung in 99% aller Faelle stattfindet, dann liegt eben auch das schon so in der Pipeline. Und dein Code fuehrt doch in einer realen Anwendung auch irgendwann zu einem Sprung. Warum sollte man das abfragen, wenn es fuer den Programmfluss keinen Unterschied macht? Mir ging es aber jetzt auch eher um um diese allgemeine Art der Argumentation, warum diese Messung nicht mit realen Daten gefuettert werden darf. Ich bin mir ziemlich sicher, das bei realen Daten wenn man alle auf einmal bearbeiten darf, ein schlauer Algorithmus schneller sein wird als dieser hier n mal. f'`8k

Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

TGGC hat geschrieben:Mir ging es aber jetzt auch eher um um diese allgemeine Art der Argumentation, warum diese Messung nicht mit realen Daten gefuettert werden darf. Ich bin mir ziemlich sicher, das bei realen Daten wenn man alle auf einmal bearbeiten darf, ein schlauer Algorithmus schneller sein wird als dieser hier n mal.
Okay, in realen Situationen wird der Code in 50% der Fälle ohne Sprung durchlaufen und in den anderen 50% beim dritten Sprung scheitern. Mag sein, dass die Testdaten darauf nicht zutreffen und dass das nicht sauber argumentiert ist, aber ob die Sprünge jetzt 100% langsamer sind als die sprungfreie Version oder 50%, das ist doch egal.

Für die reale Anwendung macht es auch kaum einen Unterschied – für das Problem existiert kein lohnenswertes intelligentes Verfahren (das mit weniger als 30 bis 100 Anweisungen auskommt, die im Moment benötigt werden), noch führt das Ergebnis der Funktion zu einem besonders großen Sprung (es ändert sich lediglich ein Datenzeiger, und das auch so früh, dass es zu keinen Cache-Problemen kommen wird). Ich sage ja immer wieder, dass es kein Bottleneck ist, sondern just for fun.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von TGGC »

Nein, ein intelligentes Verfahren, das mit noch weniger Anweisungen als die simple Brute Force Version auskommt gibt es vermutlich nicht. f'`8k


Gruß, TGGC (Was Gamestar sagt...)
Benutzeravatar
Krishty
Establishment
Beiträge: 8247
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Schnellster Test POINT ggn RECT

Beitrag von Krishty »

Achja, noch was am Rande: den ::POINT per-value zu übergeben bringt 4%, das ::RECT per-reference bringt 15%, erst das ::RECT & und danach den ::POINT zu übergeben bringt 4% (jeweils gegenüber der anderen Möglichkeit). Komischerweise auch mit inlineing (sollte man da nicht auch annehmen, dass der Optimizer alle loads optimal anordnet, egal, wie die Parameterliste aussah?) Auf die Benchmarks hatte das aber keinen Einfluss, ich habe für alle Funktionen identische Parameter benutzt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten