C++ Performance



  • Was sind eigentlich die Tricks, dass C++ so schnell ist?



  • der geschreibene code wird in ziemlich gut in maschinencode gewandelt (C/C++ ist sehr systemnah)
    und der maschinencode wird optimiert bis zum geht nicht mehr
    variablen werden wegoptimiert, beid er compilation wird schon viel ausgerechnet

    aus folgendem beispiel:

    int n = 5;
    int erg = 0, i = 0;
    for(i = 0; i < n; ++i)
       erg += i;
    

    macht der compiler ungefähr sowas hier:

    int n = 5;
    int erg = (n*(n+1))/2;
    

    und siehe da, i ist komplett weg



  • Javaspion schrieb:

    Was sind eigentlich die Tricks, dass C++ so schnell ist?

    das zero-cost-principle.

    man zahlt nur performance kosten wenn man es braucht - wenn ich etwas nicht brauche kostet es mich keine performance.

    beispiel: speicher allokationen. ich kann alles auf den stack legen was praktisch gesehen nur eine pointer addition an zeit kostet.

    oder in java ist jedes objekt ja lock-able und implementiert noch dazu das observer pattern um mit den anderen objekten kommunizieren zu koennen die gelockt sind. ob man das nun braucht oder nicht. in c++ gibt es soetwas nicht.

    selbes prinzip wird bei nahezu allen sprachfeatures verwendet - so dass man nur einen bruchteil der performance kosten von anderen sprachen zahlen muss.

    und natuerlich gibt es metaprogrammierung um statisch code zu generieren (und nichts ist schneller als code der nicht ausgefuehrt werden muss da er vorher schon berechnet wurde) und type_traits ermoeglichen es statisch die besten algorithmen fuer die jeweiligen datentypen zu verwenden.

    das bedeutet aber auch einen gewissen mehraufwand den man als programmierer taetigen muss um diese features zu nutzen. das zero-cost-principle dagegen bekommt man ohne aufwand.



  • wobei aktuelle java compiler auch schon gewaltig optimieren. und die JIT compiler diverser VMs optimieren dann noch mehr weg, so dass im endeffekt u.u. sogar derselbe maschinencode ausgeführt wird, wie bei einem c/c++ programm.
    also kurz gefasst: die hohe performance einer sprache kommt durch die optimierungsfähigkeiten des compilers zustande. und traditionsgemäß sind da die "reinen" c++ compiler am besten.



  • Skym0sh0 schrieb:

    der geschreibene code wird in ziemlich gut in maschinencode gewandelt (C/C++ ist sehr systemnah)
    und der maschinencode wird optimiert bis zum geht nicht mehr
    variablen werden wegoptimiert, beid er compilation wird schon viel ausgerechnet

    aus folgendem beispiel:

    int n = 5;
    int erg = 0, i = 0;
    for(i = 0; i < n; ++i)
       erg += i;
    

    macht der compiler ungefähr sowas hier:

    int n = 5;
    int erg = (n*(n+1))/2;
    

    und siehe da, i ist komplett weg

    das glaub ich nicht... mal abgesehen davon, dass beide Programme unterschiedliche Ergebnisse liefern (Deine Schleife müßte bis n laufen, nicht bis n-1) glaube ich nicht, dass es Compiler gibt, die das erkennen.



  • int main(int argc, char** argv) {
    	int n = 5;
    	int erg = 0, i = 0;
    	for(i = 0; i < n; ++i)
    	   erg += i; 
    
    	std::cout<<erg;
    }
    
    ;	COMDAT _main
    _TEXT	SEGMENT
    _argc$ = 8						; size = 4
    _argv$ = 12						; size = 4
    _main	PROC						; COMDAT
    
    ; 28   : 	int n = 5;
    ; 29   : 	int erg = 0, i = 0;
    ; 30   : 	for(i = 0; i < n; ++i)
    ; 31   : 	   erg += i; 
    ; 32   : 
    ; 33   : 	std::cout<<erg;
    
      00000	8b 0d 00 00 00
    	00		 mov	 ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
      00006	6a 0a		 push	 10			; 0000000aH
      00008	ff 15 00 00 00
    	00		 call	 DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z
    
    ; 34   : }
    
      0000e	33 c0		 xor	 eax, eax
      00010	c3		 ret	 0
    _main	ENDP
    _TEXT	ENDS
    END
    

    Ohne std::cout hat er es sogar ganz weg gelassen



  • Aber mit variablem n geht es nicht mehr.

    int main(int argc, char** argv) {
    	int n = argc;
    	int erg = 0, i = 0;
    	for(i = 0; i < n; ++i)
    	   erg += i; 
    
    	std::cout<<erg;
    }
    
    ;	COMDAT _main
    _TEXT	SEGMENT
    _argc$ = 8						; size = 4
    _argv$ = 12						; size = 4
    _main	PROC						; COMDAT
    
    ; 27   : int main(int argc, char** argv) {
    
      00000	53		 push	 ebx
      00001	57		 push	 edi
    
    ; 28   : 	int n = argc;
    ; 29   : 	int erg = 0, i = 0;
    ; 30   : 	for(i = 0; i < n; ++i)
    
      00002	8b 7c 24 0c	 mov	 edi, DWORD PTR _argc$[esp+4]
      00006	33 d2		 xor	 edx, edx
      00008	33 c9		 xor	 ecx, ecx
      0000a	33 db		 xor	 ebx, ebx
      0000c	33 c0		 xor	 eax, eax
      0000e	83 ff 02	 cmp	 edi, 2
      00011	7c 12		 jl	 SHORT $LC9@main
      00013	56		 push	 esi
      00014	8d 77 ff	 lea	 esi, DWORD PTR [edi-1]
    $LL10@main:
    
    ; 31   : 	   erg += i; 
    
      00017	03 d0		 add	 edx, eax
      00019	8d 4c 01 01	 lea	 ecx, DWORD PTR [ecx+eax+1]
      0001d	83 c0 02	 add	 eax, 2
      00020	3b c6		 cmp	 eax, esi
      00022	7c f3		 jl	 SHORT $LL10@main
      00024	5e		 pop	 esi
    $LC9@main:
    
    ; 28   : 	int n = argc;
    ; 29   : 	int erg = 0, i = 0;
    ; 30   : 	for(i = 0; i < n; ++i)
    
      00025	3b c7		 cmp	 eax, edi
      00027	7d 02		 jge	 SHORT $LN8@main
    
    ; 31   : 	   erg += i; 
    
      00029	8b d8		 mov	 ebx, eax
    $LN8@main:
      0002b	03 ca		 add	 ecx, edx
    
    ; 32   : 
    ; 33   : 	std::cout<<erg;
    
      0002d	03 cb		 add	 ecx, ebx
      0002f	51		 push	 ecx
      00030	8b 0d 00 00 00
    	00		 mov	 ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
      00036	ff 15 00 00 00
    	00		 call	 DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z
      0003c	5f		 pop	 edi
    
    ; 34   : }
    
      0003d	33 c0		 xor	 eax, eax
      0003f	5b		 pop	 ebx
      00040	c3		 ret	 0
    _main	ENDP
    _TEXT	ENDS
    END
    


  • Hm, cool. 🙂

    Aber ich glaube nicht, dass er sieht, dass er die Schleife durch ne einfache Berechnung ersetzen kann. Vielmehr sieht der wohl in der Datenflußanalyse, dass er den Wert direkt berechnen kann und tut das dann -- indem er die Schleife ausführt. Mehr braucht man von einem Compiler ja auch nicht zu erwarten. Aus der Schleife den Ausdruck n*(n+1)/2 zu machen ist imo die Aufgabe des Programmierers.


Anmelden zum Antworten