Polymorphie und moderne Compiler
-
Hi,
danke für die Arbeit !!
Es bringt in meinem Fall nicht viel, selbst Ergebnisse zu liefern, weil ich auch den xLC unter AIX habe....
Aber es sind einige sehr interessante Sachen dabei !!
z.B: hätte ich Folgendes spontan nicht erwartet (bin allerdings auch kein "Optimierer"):
tntnet schrieb:
..
ohne Optimierung:
static via ref : 100000000 1.29718
inline via ref : 100000000 1.82919Inline 50% langsamer als static

tntnet schrieb:
..
ohne Optimierung:
static via ref : 100000000 1.29718
static via ptr : 100000000 1.83586Pointer 50% langsamer als Referenz

tntnet schrieb:
..
mit -O5:
static via ref : 100000000 1.19209e-06Da wird doch wohl auch ein inline hinterstecken, oder ?
Danke,
Simon2.
-
@tntnet: Zum Vergleich könntest du auch noch die selben Aufrufe für ein Stack-Objekt
A ob;durchführen
Da dürfte der Compiler auch eine Chance haben, die virtuellen Aufrufe zu optimieren.
-
Simon2 schrieb:
tntnet schrieb:
..
ohne Optimierung:
static via ref : 100000000 1.29718
inline via ref : 100000000 1.82919Inline 50% langsamer als static

Halte ich z.B. für unrealistisch. Sind solche Benchmark-Ergebnisse signifikant? Handelt es sich da vll. um reine Zufallsergebnisse weil die Unterschiede zu klein sind? Wie sieht der Assember-Code davon aus?
-
CStoll schrieb:
@tntnet: Zum Vergleich könntest du auch noch die selben Aufrufe für ein Stack-Objekt
A ob;durchführen
Da dürfte der Compiler auch eine Chance haben, die virtuellen Aufrufe zu optimieren.Ok. Ich hatte das weggelassen, da ich das nicht für relevant halte, aber Du hast schon recht, daß man das natürlich auch verifizieren könnte. Also habe ich folgende Änderungen gemacht:
[] in a.h den Konstruktor public gemacht
[] eine Zusätliche Deklaration "A s;"
[*] folgende neue Tests:T("virtual auf stack", s.inc_v(), s.get()); T("static auf stack", s.inc_s(), s.get()); T("inline auf stack", s.inc_i(), s.get());Die Resultate:
ohne Optimierung:
virtual auf stack: 100000000 1.04899
static auf stack: 200000000 1.84272
inline auf stack: 300000000 3.08779mit -O2:
virtual auf stack: 100000000 0.685513
static auf stack: 200000000 0.689291
inline auf stack: 300000000 9.53674e-07und mit -O5:
virtual auf stack: 100000000 1.19209e-06
static auf stack: 200000000 9.53674e-07
inline auf stack: 300000000 1.19209e-06An der Stelle fällt mir auf, daß der reset-Aufruf falsch ist. Aber das ist ja egal. Merkwürdig ist, daß ohne Optimierung die Geschwindigkeit genau verdreht ist. Ansonsten wird der virtuelle Aufruf durch eine statischen Aufruf ersetzt.
Konrad Rudolph schrieb:
Simon2 schrieb:
tntnet schrieb:
..
ohne Optimierung:
static via ref : 100000000 1.29718
inline via ref : 100000000 1.82919Inline 50% langsamer als static

Halte ich z.B. für unrealistisch. Sind solche Benchmark-Ergebnisse signifikant? Handelt es sich da vll. um reine Zufallsergebnisse weil die Unterschiede zu klein sind? Wie sieht der Assember-Code davon aus?
Unrealistisch? Das geht aus der Konsole direkt über die Zwischenablage in den Browser. Mehrfache Wiederholungen bringen sehr ähnliche Ergebnisse.
Ein Resümee für mich (und Bestätigung meiner bisherigen Vorgehensweise): Programmiere so, wie es richtig ist und überlasse dem Compiler die Optimierung (zumindest auf dieser Ebene). Algorithmen sind Aufgabe des Entwicklers, die einzelnen Bits und Bytes an die richtige Stelle zu schieben, macht der Compiler.
Höhere Optimierungslevel verlängern aber die Compilezeit beträchtlich, so daß man beim Entwickeln sparsam damit umgehen sollte.
Tntnet
-
tntnet schrieb:
Konrad Rudolph schrieb:
Halte ich z.B. für unrealistisch. Sind solche Benchmark-Ergebnisse signifikant? Handelt es sich da vll. um reine Zufallsergebnisse weil die Unterschiede zu klein sind? Wie sieht der Assember-Code davon aus?
Unrealistisch? Das geht aus der Konsole direkt über die Zwischenablage in den Browser. Mehrfache Wiederholungen bringen sehr ähnliche Ergebnisse.
Na ja, ich wollte nicht behaupten, dass Du die Ergebnisse fälschst sondern dass der Compiler entweder Deine Inline-Anweisung missachtet oder dass die Ergebnisse einfach so gering unterschiedlich sind, dass die angezeigten Unterschiede durch andere Faktoren zustande kommen.
In jedem Fall halte ich solche Ergebnisse für unzuverlässig solange man nicht das tatsächliche Ergebnis des Compilers anschaut --> als den Assemblercode. Die Zahlen weisen hier z.B. deutlich darauf hin, dass inc_i eben *nicht* ge-inlined wird. Wie kann man diese Ergebnisse sonst erklären?
-
Wie Konrad schon sagt, solche Ergebnisse als signifikante Benchmark-Ergebnisse zu sehen wäre falsch.
Jedoch finde ich zeigen sie sehr gut das Compiler nicht spezifiziert sind wie was zu Optimieren ist bzw wie sie vorgehen, rein sprachlich bedingt.
Das fängt an das sie Übergabewerte in der Reinfolge von links nach rechts oder von rechts nach links erstellen können, das sie Rechnungen mit Konstanten zur Kompilezeit ausrechen oder nicht usw... Auch das inline Schlüsselwort ist leider nicht "zuverlässig" da es nur als eventuelle Optimierung gedacht ist, der Compiler jedoch nicht verplichtet ist es umzusetzten. Im Gegenzug kann er aber jede Funktion als inline erstellen auch wenn diese nicht als solche gekennzeichent ist.Ganz im Gegensatz zum Java(Sun) Standart, dort steht genau beschrieben wie alles zu geschehen hat. Ob sich da alle Compiler und VM Hersteller dran halten ist wieder eine andere Sache.
Auch muss ich Tntnet zustimmen, programmiere so wie es (für das Team/Dich) richtig ist und überlass dem Compiler den Rest (Bitoptimierung).
Falls man wirklich Optimieren muss/will sollte man sich ohnehin nur auf den Code beschrenken der Performance relevant ist. Was Erfahrungsgemäß höchstens 1/4 ist, den Rest kann man getrost so lassen wie er ist.
Natürlich kosten virtuelle Aufrufe mehr Zeit als statische oder inline Derivate , jedoch würde ich als letztes anfangen diese zu Optimieren. Was die virtuellen Basisklassen betrifft sollte man normalerweise schon eine Menge Performance sparen wenn man sie als reines Interface deklariert. (Schon komisch das Java genau das macht
)Zum Schluß möchte ich noch loswerden das ich sowas schon als "Schwanzvergleichen" betrachte wenn man Java und C++ Performance mäßig gegenüberstellt. Java ist nicht die schnellste Sprache doch dafür wurde sie auch nicht konzepiert. Wie dem auch sei, ich zieh mich hier auch besser zurück bevor mich hinreißen lasse.
Baracke
PS: Das mit den Optimierungen bezüglich Compilern beruht nur auf sogenannten erlesem Wissen weniger auf überprüften

-
hab einen test unter win32 gemacht.
os: WinXP SP2
prozessor: Pentium D (3.4 GHz)
ram: 2 GB
compiler: g++ 3.4.5Habe mal wegen Konrad Rudolphs Einwand bzgl. der kleinen Zeiten den max Wert auf 1e9 gestellt:
ohne optimierung, mit debug-info (-g):
virtual via ref: 1000000000 6.27763
static via ref : 1000000000 5.82477
inline via ref : 1000000000 5.73107
virtual via ptr: 1000000000 6.30886
static via ptr : 1000000000 5.65299
inline via ptr : 1000000000 5.66861mit optimierung (-O2)
virtual via ref: 1000000000 5.88723
static via ref : 1000000000 3.76346
inline via ref : 1000000000 0.062464
virtual via ptr: 1000000000 5.91846
static via ptr : 1000000000 3.84154
inline via ptr : 1000000000 0.593408mit optimierung (-O3)
virtual via ref: 1000000000 6.04339
static via ref : 1000000000 1.93638
inline via ref : 1000000000 1.93638
virtual via ptr: 1000000000 6.09024
static via ptr : 1000000000 1.93638
inline via ptr : 1000000000 1.93638Wie tntnet schon sagte im Normalfall, reicht die Performance von virtual (1 Milliarde Aufrufe in ca. 6 Sekunden).
Würde mich mal interessieren wie lange Java/.NET für 1e9 einfache, virtuelle Aufrufe brauchen.
Wieder_zu_Faul schrieb:
Zum Schluß möchte ich noch loswerden das ich sowas schon als "Schwanzvergleichen" betrachte wenn man Java und C++ Performance mäßig gegenüberstellt. Java ist nicht die schnellste Sprache doch dafür wurde sie auch nicht konzepiert. Wie dem auch sei, ich zieh mich hier auch besser zurück bevor mich hinreißen lasse.
Wenn ich die Möglichkeit habe unter verschiedenen Schwänzen zu wählen, vergleiche ich sie auch

-
Wenn ich die Möglichkeit habe unter verschiedenen Schwänzen zu wählen, vergleiche ich sie auch
Aber bitte nicht hier im C++ Forum. *brrr* Danke.
Im ernst, nicht schon wieder einen Java vs. C++ Thread.
Nicht im ISO-C++-Forum!!!
-
Compilerbauer schrieb:
hustbaer schrieb:
Und die einzigen Dinge die nach meinem Kenntnisstand in C++ wirklich weh tun sind virtuelle Basisklassen und dynamic casts. Ggf. noch typeid, bloss das braucht man eh sogut wie nie (ich zumindest).
Genau das ist dann starker Gebrauch von Polymorphie. Ich habe eine rein abstrakte(virtual) Basisschicht mit vielen Klassen, für welche es dann verschiedene Implementierungen gibt.
Ich meinte "virtual inheritance", nur falls da jetzt ein Misverständnis besteht. Also nicht rein abstrakte Basisklassen.
Sowas halt:class base {}; class derived : public virtual base {};
-
Bin mal an VC++ 2k5 rangekommen. (x86)
Mit PGO drüber gefahren ergab:virtual via ref: 1000000000 1.7403 static via ref : 1000000000 1.07885 inline via ref : 1000000000 1.07864 virtual via ptr: 1000000000 1.80288 static via ptr : 1000000000 1.08437 inline via ptr : 1000000000 1.07547also auch jeweils 10^9 Aufrufe. (Profiled wurde für 10^8)
Musste allerdings bisschen den Code modifizieren. ^^Poste mal asm output noch dazu:
virtual via ref/ptr:mov ecx, DWORD PTR ?theA@A@@0V1@A mov edi, eax mov esi, 1000000000 ; 3b9aca00H npad 6 $LL18@main: mov eax, DWORD PTR [ecx] cmp eax, OFFSET ?inc_v@A@@UAEXXZ ; A::inc_v jne $LN22@main ; taken 0(0%), not-taken 100000000(100%) add DWORD PTR ?theA@A@@0V1@A+4, 1 $LN17@main: sub esi, 1 jne SHORT $LL18@main ; taken 99999999(99%), not-taken 1(0%)zusätzlich noch weiter weg:
; Scenario dead code below $LN22@main: ; 26 : T("virtual via ref", a.inc_v(), a.get()); mov ecx, OFFSET ?theA@A@@0V1@A ; A::theA call eax mov ecx, DWORD PTR ?theA@A@@0V1@A jmp $LN17@mainDas mit den Heuristiken ist damit für den MS VC++ belegt.
static/inline via ref/ptr:mov ecx, DWORD PTR ?theA@A@@0V1@A+4 mov edx, 1000000000 ; 3b9aca00H npad 7 $LL15@main: add ecx, 1 sub edx, 1 jne SHORT $LL15@main ; taken 99999999(99%), not-taken 1(0%) mov DWORD PTR ?theA@A@@0V1@A+4, ecxInline wird bei beiden durchgeführt, allerdings ist die Schleife lächerlich. Ob das auf real-existierenden Code auswirkungen hat kann ich nicht sagen. Können einige Compiler diese schleife komplett wegoptimieren und durch eine einzige mov instruction ersetzen?`
MfG
DDR-RAM
-
Ich habe hier noch ein paar Ergebnisse. Diesmal auf Linux (Kubuntu 6.10, AMD64-3000, gcc 4.1.2):
ohne Optimierung:
virtual via ref : 100000000 1.17056 static via ref : 200000000 0.918881 inline via ref : 300000000 0.864239 virtual via ptr : 400000000 1.26863 static via ptr : 500000000 0.863478 inline via ptr : 600000000 0.942093 virtual auf stack: 100000000 0.863628 static auf stack : 200000000 0.864764 inline auf stack : 300000000 0.863548mit -O2:
virtual via ref : 100000000 0.361489 static via ref : 200000000 0.358389 inline via ref : 300000000 1.19209e-06 virtual via ptr : 400000000 0.362769 static via ptr : 500000000 0.358353 inline via ptr : 600000000 9.53674e-07 virtual auf stack: 100000000 0.359503 static auf stack : 200000000 0.36071 inline auf stack : 300000000 0und mit -O3:
virtual via ref : 100000000 0.435069 static via ref : 200000000 0.359825 inline via ref : 300000000 0 virtual via ptr : 400000000 0.358871 static via ptr : 500000000 0.359128 inline via ptr : 600000000 0 virtual auf stack: 100000000 0.365479 static auf stack : 200000000 0.358852 inline auf stack : 300000000 0hier zeigt sich kaum ein Unterschied zwischen virtuellen und statischen Aufrufen. Inline-Aufrufe werden weg optimiert.
Ich bin bei 1e8 Aufrufen geblieben. Die Ergebnisse sind auch so gut reproduzierbar.
Die bisherigen Ergebnisse finde ich sehr Interessant und aufschlußreich. Ich wundere mich ein wenig, daß die beiden Compiler, die mir zur Verfügung stehen, die inline-Aufrufe vollständig weg optimieren können, aber die bisherigen Ergebnisse für VC++ darauf hindeuten, daß dieser es nicht kann. Beim Gcc sieht man doch deutlich eine Verbesserung von 3.x auf 4.1.x.
(ich bin übrigens auch dafür, daß wir hier nicht über andere Programmiersprachen her ziehen. Das tun wir oft genug. Und noch was an die Moderatoren: wie wäre es mit einem Flamewar-Forum?)
Tntnet
-
Ich glaube da ist eine Art Bug/Feature in dem Compiler, denn wenn ich nicht PGO optimiere und statt /Ob2 /Ob1 verwende, dann wird geinlined
Also:
/Ob1 (only __inline)virtual via ref: 100000000 0.377901 static via ref : 100000000 0.323458 inline via ref : 100000000 8.38095e-006 virtual via ptr: 100000000 0.444236 static via ptr : 100000000 0.35439 inline via ptr : 100000000 1.00571e-005/Ob2 (any suitable)
virtual via ref: 100000000 0.352203 static via ref : 100000000 0.113812 inline via ref : 100000000 0.108536 virtual via ptr: 100000000 0.37692 static via ptr : 100000000 0.108227 inline via ptr : 100000000 0.110387Also /Ob1 bzw. /Ob2 bestimmt, welche Funktionen evtl. inlined werden, dabei werden bei /Ob1 nur solche betrachtet, die in irgendeinerweise durch den Programmierer gekennzeichnet wurden und bei /Ob2 werden (fast) alle Funktionen betrachtet.
Weiß jemand mehr darüber? Oder soll ich da mal im entsprechenden Compilerforum posten?
-
Entschuldigung,
dass ich den alten Thread nochmal rauskrame, aber vielleicht interessiert es hier ja jemanden. (eigentlich falsches Forum aber der Thread hatte sich so entwickelt)
Ich habe mich bei MS erkundigt (Feedback), weshalb die Schleife so schlecht von deren Compiler optimiert wird. Es ist ein Bug.
QuelleMfG
DDR-RAM