Operator schneller als Inline Assembler
-
Hallo. Ich bin neu hier und komme eigendlich aus dem .net Bereich. Jetzt habe ich mich mal an c++ rangewagt. Jetzt habe ich versucht den + Operator von int in Assembler nachzubilden und habe dabei etwas merkwürdiges festgestellt. Beide Methoden produzieren den gleichen(!) Maschinencode. Trotzdem ist die 'Standardlösung' schneller. Hier mal der Code
int _add(int i, int j) { __asm { mov eax, i add eax, j } } int _add2(int i, int j) { return i + j; }Wenn man das kompiliert hat man die gleichen Ergebnisse, die aber unterschiedlich schnel sind. Ich wollte mal fragen, woran das liegen kann?
Gruß pdelvo
-
Miss nochmal. Sind sie wirklich unterschiedlich? Das glaube ich erstmal nicht. Und dann, wenn es echt einen Unterschied gab, schau den erzeugten Assemlercode an und zeig ihn her.
-
Wie hast du gemessen?
Das Thema hat wenig mit C++ zu tun und wäre im Assembler-Forum besser aufgehoben.
-
Registrierter Troll schrieb:
Das Thema hat wenig mit C++ zu tun und wäre im Assembler-Forum besser aufgehoben.
Die Fähigkeiten der C++-Compiler auszuloten ist 100% on-topic.
-
Ich kann mir vorstellen, dass der Kompiler bei der Standardlösung die Methode inlined. Bei der Lösung mit Assembler ist diese Optimierung sicherlich schwieriger. Manche Kompiler schalten bei Inline-Assembler viele Optimierungen ab. Das ist auch eigentlich richtig. Denn, wenn ich Inline-Assembler verwende, sage ich ja explizit, dass ich es besser kann als der Kompiler.
-
Ich habe die Ticks gezählt. Ich habe den Test mehrfach wiederholt. Meine Version braucht bei mir ca. 2400 Ticks, der Operator 2270(Als debug kompiliert, sonst Operator 0(wird wahrscheinlich wegoptimiert), Inline 24).
#include "stdafx.h" #include <iostream> #include <windows.h> using namespace std; int _add(int i, int j); int _add2(int i, int j); int _tmain(int argc, _TCHAR* argv[]) { int c1 = GetTickCount(); for(int i = 0;i < 100000000; i++) { int z = _add(100,100); } c1 = GetTickCount() - c1; cout << c1 << endl; c1 = GetTickCount(); for(int i = 0;i < 100000000; i++) { int z = _add2(100,100); } c1 = GetTickCount() - c1; cout << c1 << endl; cin >> c1; return 0; } int _add(int i, int j) { __asm { mov eax, i add eax, j } } int _add2(int i, int j) { return i + j; }Gruß pdelvo
-
volkard schrieb:
Registrierter Troll schrieb:
Das Thema hat wenig mit C++ zu tun und wäre im Assembler-Forum besser aufgehoben.
Die Fähigkeiten der C++-Compiler auszuloten ist 100% on-topic.
Welchen Code ein x-beliebiger Compiler auf einer x-beliebigen Plattform erzeugt und wie schnell oder langsam der erzeugte Code ist, scheint mir persönlich wenig mit C++ im Sinne dieses Subforums zu tun haben. Genauso wenig wie x86-Assemblercode hier on-topic ist. Dafür gibt es die Compiler- und Assembler-Subforen, die imho besser passen.
Forenbeschreibung schrieb:
Fragen zu bestimmten Funktionen und Abläufen in C++ (nach dem ISO-Standard)
...aber du bist der Moderator, nicht ich.
-
Jetzt noch den Assembler-Code anzeigen.
Mit Alt+F8 oder so.
-
pdelvo schrieb:
Ich habe die Ticks gezählt. Ich habe den Test mehrfach wiederholt. Meine Version braucht bei mir ca. 2400 Ticks, der Operator 2270(Als debug kompiliert, sonst Operator 0(wird wahrscheinlich wegoptimiert)
Debug sagt wenig aus. mach ne Summe aus der Zuweisung und gibd as Ergebnis am Ende aus. Dann kann er das auch im Release nicht wegoptimieren.
-
01271B50 push ebp 01271B51 mov ebp,esp 01271B53 sub esp,0C0h 01271B59 push ebx 01271B5A push esi 01271B5B push edi 01271B5C lea edi,[ebp-0C0h] 01271B62 mov ecx,30h 01271B67 mov eax,0CCCCCCCCh 01271B6C rep stos dword ptr es:[edi] 01271B6E mov eax,dword ptr [i] 01271B71 add eax,dword ptr [j]Er ist bei beiden Versionen vollkommen identisch.
Schlimm ist das zwar nicht, aber es verwundert mich.Menu Debuggen/Fenster/Disassembly
Gruß pdelvo
-
Registrierter Troll schrieb:
Welchen Code ein x-beliebiger Compiler auf einer x-beliebigen Plattform erzeugt und wie schnell oder langsam der erzeugte Code ist, scheint mir persönlich wenig mit C++ im Sinne dieses Subforums zu tun haben. Genauso wenig wie x86-Assemblercode hier on-topic ist. Dafür gibt es die Compiler- und Assembler-Subforen, die imho besser passen.
Ich erwarte exemplarische Ergebnisse, die im Prinzip auf alle Plattformen und alle aktuellen Compiler übertragbar sind. Ob der Compiler aus dem + ein ADD oder LEA macht, ist völlig egal. Wichtig sind nur die übertragbaren Sachen, zum Beispiel, daß man nie nie nie seine Summen in Zeitmessprogrammen wegwerfen darf, sondern sie ausgeben muß, weil sonst der Compiler ganze Blöcke wegoptimieren kann. Und daß man die Summen nicht zu einfach berechnen darf, also daß der Compiler statt wiederholtem + ein * machen kann. Und daß der Einsatz von Inline-Assembler das Programm potenziell verlangsamt, weil man den Compiler einschränkt.
-
pdelvo schrieb:
Er ist bei beiden Versionen vollkommen identisch.
Schlimm ist das zwar nicht, aber es verwundert mich.Das ist schlimm! Ich würde an Deiner Stelle jetzt panisch im Kreis laufen. Was hast Du gemessen? Solange der Messfehler nicht geklärt ist, kannst Du nie wieder etwas messen und dem Ergebnis glauben.
-
Ich habe die Ticks, die zwichen der Messung vergangen sind gemessen. Gibt es noch eine bessere Methode?
Gruß pdelvo
-
volkard schrieb:
Jetzt noch den Assembler-Code anzeigen.
Mit Alt+F8 oder so.edit: es ist btw alt+8 ^^
aber den der release-version ^^
ich habs ma selbst gemacht mit msvc im release-mode. er übergibt die fkt-parameter nicht direkt in registern, sondern über den stack... ich hab aber auf die schnelle keine Calling-Convention gefunden, die das behebt... bin mir auch nicht ganz sicher, warum er 2 parameter per stack übergibt und nicht über register wie gesagt: asm deaktiviert eben viele optimierungen...
__declspec(noinline)ist wie immer dazu da, um eine bessere Übersicht zu haben, weil er die Fkt nicht inlined (imho MSVC spezifisch)__declspec(noinline) int _add2(int i, int j) { return i + j; 001B1010 add eax,ecx }__declspec(noinline) int _add(int i, int j) { __asm { mov eax, i 001B1000 mov eax,dword ptr [esp+4] add eax, j 001B1004 add eax,dword ptr [esp+8] }bb
-
unskilled schrieb:
ich hab aber auf die schnelle keine Calling-Convention gefunden, die das behebt... bin mir auch nicht ganz sicher, warum er 2 parameter per stack übergibt und nicht über register wie gesagt: asm deaktiviert eben viele optimierungen...
__fastcall
-
volkard schrieb:
unskilled schrieb:
ich hab aber auf die schnelle keine Calling-Convention gefunden, die das behebt... bin mir auch nicht ganz sicher, warum er 2 parameter per stack übergibt und nicht über register wie gesagt: asm deaktiviert eben viele optimierungen...
__fastcall
hatte ich probiert, aber machte es nur noch schlimmer:
__declspec(noinline) int __fastcall _add(int i, int j) { 00021000 sub esp,8 00021003 mov dword ptr [esp],edx 00021006 mov dword ptr [esp+4],ecx __asm { mov eax, i 0002100A mov eax,dword ptr [esp+4] add eax, j 0002100E add eax,dword ptr [esp] } }bb
-
pdelvo schrieb:
Ich habe die Ticks, die zwichen der Messung vergangen sind gemessen. Gibt es noch eine bessere Methode?
Mein erprobtes Schema: 1000 kleine Messungen machen und die nehmen, die am wenigsten Zeit brauchte. Immer, wenn in der Schleife eine bessere Zeit gefunden wurde, den Zähler wieder auf 1000 stellen.
Damit kriegst Du gerne fünf korrekte Stellen.