Warum wird der Code nicht optimiert?



  • Opti schrieb:

    Ok danke,
    aber das mit den zwei Zeigern versteh ich irgendwie noch nicht,
    was soll das bringen, bzw. warum sollte das schneller als eine Addition mit __int64 sein?

    Schneller nicht. Aber es muss dann im jeden Schritt, um die Länge zu bestimmen, eine Subtraktion durchgeführt werden - durchaus kritisch.



  • Sone schrieb:

    Opti schrieb:

    Ok danke,
    aber das mit den zwei Zeigern versteh ich irgendwie noch nicht,
    was soll das bringen, bzw. warum sollte das schneller als eine Addition mit __int64 sein?

    Schneller nicht. Aber es muss dann im jeden Schritt, um die Länge zu bestimmen, eine Subtraktion durchgeführt werden - durchaus kritisch.

    Wobei das Ergebnis immer gleich bleibt und der Optimierer die Subtraktion aus der Schleife herausziehen könnte.



  • http://gcc.godbolt.org/
    Lass ich mal hier.

    #include <string>
    
    using namespace std;
    
    void foo()
    {
      string s = "Hello World!"; 
      int length = s.length(); 
    
      unsigned long long sum = 0;
    
      for(int i = 0; i < 1000000000; ++i) 
      { 
          for(int j = 0; j < length; ++j) 
          { 
              sum += length; 
          } 
      }
    }
    
    .LC0:
    	.string	"Hello World!"
    foo():
    	subq	$40, %rsp
    	movl	$.LC0, %esi
    	leaq	16(%rsp), %rdi
    	leaq	15(%rsp), %rdx
    	call	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
    	movq	16(%rsp), %rax
    	leaq	-24(%rax), %rdi
    	cmpq	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_empty_rep_storage, %rdi
    	jne	.L9
    .L1:
    	addq	$40, %rsp
    	ret
    .L9:
    	leaq	16(%rdi), %rcx
    	movl	__gthrw_pthread_cancel(unsigned long), %edx
    	testq	%rdx, %rdx
    	je	.L4
    	orl	$-1, %edx
    	lock xaddl	%edx, (%rcx)
    .L5:
    	testl	%edx, %edx
    	jg	.L1
    	leaq	15(%rsp), %rsi
    	call	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_M_destroy(std::allocator<char> const&)
    	jmp	.L1
    .L4:
    	movl	-8(%rax), %edx
    	leal	-1(%rdx), %ecx
    	movl	%ecx, -8(%rax)
    	jmp	.L5
    
    #include <string>
    
    using namespace std;
    
    void foo()
    {
      string s = "Hello World!"; 
      int length = s.length(); 
    
      unsigned long long sum = 0;
    
      for(int i = 0; i < 1000000000; ++i) 
      { 
          for(int j = 0; j < s.length(); ++j) 
          { 
              sum += s.length(); 
          } 
      }
    }
    
    .LC0:
    	.string	"Hello World!"
    foo():
    	subq	$40, %rsp
    	movl	$.LC0, %esi
    	leaq	15(%rsp), %rdx
    	leaq	16(%rsp), %rdi
    	call	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
    	movq	16(%rsp), %rdx
    	leaq	-24(%rdx), %rdi
    	cmpq	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_empty_rep_storage, %rdi
    	jne	.L9
    .L1:
    	addq	$40, %rsp
    	ret
    .L9:
    	leaq	16(%rdi), %rcx
    	movl	__gthrw_pthread_cancel(unsigned long), %eax
    	testq	%rax, %rax
    	je	.L4
    	movl	$-1, %eax
    	lock xaddl	%eax, (%rcx)
    .L5:
    	testl	%eax, %eax
    	jg	.L1
    	leaq	15(%rsp), %rsi
    	call	std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_M_destroy(std::allocator<char> const&)
    	addq	$40, %rsp
    	ret
    .L4:
    	movl	-8(%rdx), %eax
    	leal	-1(%rax), %ecx
    	movl	%ecx, -8(%rdx)
    	jmp	.L5
    


  • von mir noch der Hinweis: std::string ist von Compiler zu Compiler sehr unterschiedlich implementiert, demzufolge wird auch CodeA und CodeB unterschiedlich optimiert

    der GCC std::string enthält nur einen Pointer, alle anderen Daten sind in dem Speicherbereich, auf den der Pointer vereist. der MSVC std::string enthält einen 16 Byte Buffer für kleine Strings



  • Aber wie kann es sein, dass string::length() schneller ist als eine einfache Addition?



  • Opti schrieb:

    Aber wie kann es sein, dass string::length() schneller ist als eine einfache Addition?

    -O3 nennt man auch den Götterhammer.
    Der macht alles, was Du trivialerweise auch per Hand könntest. Und noch viele Tricks, die Du noch gar nicht kennst.



  • Opti schrieb:

    Wieso ist CodeA langsamer als CodeB?

    CodeA:

    string s = "Hello World!";
    int length = s.length();
    	
    unsigned __int64 sum = 0;
    for(int i = 0; i < 1000000000; ++i)
    {
    	for(int j = 0; j < length; ++j)
    	{
    		sum += length;
    	}
    }
    

    CodeB:

    string s = "Hello World!";
    
    unsigned __int64 sum = 0;
    for(int i = 0; i < 1000000000; ++i)
    {
    	for(int j = 0; j < s.length(); ++j)
    	{
    		sum += s.length();
    	}
    }
    

    CodeA braucht 11540ms.
    CodeB braucht 7090ms.

    Ein guter Compiler müsste das auf 1ms reduzieren, ist ja alles konstant.



  • also schrieb:

    Ein guter Compiler müsste das auf 1ms reduzieren, ist ja alles konstant.

    Meinst du, der Compiler erkennt, dass sum = s.length()^2 * 10^9 ist und die Schleifen weg lässt?



  • also schrieb:

    Opti schrieb:

    Wieso ist CodeA langsamer als CodeB?

    CodeA:

    string s = "Hello World!";
    int length = s.length();
    	
    unsigned __int64 sum = 0;
    for(int i = 0; i < 1000000000; ++i)
    {
    	for(int j = 0; j < length; ++j)
    	{
    		sum += length;
    	}
    }
    

    CodeB:

    string s = "Hello World!";
    
    unsigned __int64 sum = 0;
    for(int i = 0; i < 1000000000; ++i)
    {
    	for(int j = 0; j < s.length(); ++j)
    	{
    		sum += s.length();
    	}
    }
    

    CodeA braucht 11540ms.
    CodeB braucht 7090ms.

    Ein guter Compiler müsste das auf 1ms reduzieren, ist ja alles konstant.

    Genau. Habe den Code gerade nicht probiert. Aber habe die GCC mit -O3 schon oft erlebt, wie sie verschachtelte Summierschleifen erkannt hat und zu einer simplen Multiplikation aufgelöst hat. Einfach frech. Superfrech.
    Kommt wohl auch ein wenig daher, daß jeder Hans und Kunz dran rumpopeln darf (uns soll!). Wenn ein H&K am eigenen Code sieht, daß da eine Optimierung vom Compiler übernommen werden könnte, fummelt er sie halt rein.
    Derzeit macht GCC schnelleren Code als MS. So viele Ideen kann man nicht einkaufen.

    Aber MSVC6.1 war härter drauf. Ein Messprogramm, das mit new[] Speicher besorgt hat und nur den Speicher in komplextester Weise beschrieben, gelesen, verrechnet hat, und danach ihn mit delete[] sauber gelöscht hat, aber keine Ausgabe hatte, wurde zu 0ms optimiert. Soweit ich mich erinnere, sind aktuelle Compiler nicht mehr so frech.



  • Aber ein guter Compiler kann das:

    // Ursprungscode (platzsparend formatiert)
    string s = "Hello World!";
    unsigned __int64 sum = 0;
    for(int i = 0; i < 1000000000; ++i)
        for(int j = 0; j < s.length(); ++j)
            sum += s.length();
    
    // loop interchange
    for(int j = 0; j < s.length(); ++j)
        for(int i = 0; i < 1000000000; ++i)
            sum += s.length();
    
    // Die 1000000000 Additionen in eine Multiplikation
    for(int j = 0; j < s.length(); ++j)
        sum += s.length()*1000000000;
    
    // Die s.length() Additionen in eine Multiplikation
    unsigned __int64 sum = 0;
    sum += s.length()*s.length()*1000000000;
    
    // =0 ist unnötig
    string s = "Hello World!";
    unsigned __int64 sum = s.length()*s.length()*1000000000;
    


  • Letzter Schritt:

    // unsigned __int64 sum wird nicht gebraucht; einfach weglassen
    string s = "Hello World!";
    

Anmelden zum Antworten