C vs Go-Schleifen und einfache Mathematik

Als ich es satt hatte, wie viele in C zu programmieren, interessierte ich mich für Go. Es ist streng typisiert, kompiliert und daher sehr produktiv. Und dann wollte ich herausfinden, wie verwirrt die Macher von Go waren, ihre Arbeit mit Schleifen und Zahlen zu optimieren.

Zunächst schauen wir uns an, wie es C geht.

Wir schreiben so einen einfachen Code:

#include <stdint.h> #include <stdio.h> int main() { uint64_t i; uint64_t j = 0; for ( i = 10000000; i>0; i--) { j ^= i; } printf("%lu\n", j); return 0; } 

Mit O2 kompilieren, zerlegen:

 564: 31 d2 xor %edx,%edx 566: b8 80 96 98 00 mov $0x989680,%eax 56b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 570: 48 31 c2 xor %rax,%rdx 573: 48 83 e8 01 sub $0x1,%rax 577: 75 f7 jne 570 <main+0x10> 

Wir bekommen die Ausführungszeit:

echte 0m0,023s
Benutzer 0m0,019s
sys 0m0,004s

Es scheint, dass es keinen Ort gibt, an dem man beschleunigen kann, aber wir haben einen modernen Prozessor. Für solche Operationen haben wir schnelle SSE-Register. Wir versuchen die Optionen gcc -mfpmath = sse -msse4.2 das Ergebnis ist das gleiche.
Add -O3 und Prost:

  57a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 580: 83 c0 01 add $0x1,%eax 583: 66 0f ef c8 pxor %xmm0,%xmm1 587: 66 0f d4 c2 paddq %xmm2,%xmm0 58b: 3d 40 4b 4c 00 cmp $0x4c4b40,%eax 590: 75 ee jne 580 <main+0x20> 

Es ist ersichtlich, dass SSE2-Befehle und SSE-Register verwendet werden, und wir erhalten eine dreifache Leistungssteigerung:

echte 0m0,006s
Benutzer 0m0,006s
sys 0m0,000s

Auch unterwegs:

 package main import "fmt" func main() { i := 0 j := 0 for i = 10000000; i>0; i-- { j ^= i } fmt.Println(j) } 

 0x000000000048211a <+42>: lea -0x1(%rax),%rdx 0x000000000048211e <+46>: xor %rax,%rcx 0x0000000000482121 <+49>: mov %rdx,%rax 0x0000000000482124 <+52>: test %rax,%rax 0x0000000000482127 <+55>: ja 0x48211a <main.main+42> 


Timings gehen:
regelmäßig gehen:
echte 0m0,021s
Benutzer 0m0,018s
sys 0m0,004s

gccgo:
echte 0m0,058s
Benutzer 0m0,036s
sys 0m0,014s

Die Leistung, wie im Fall von C und O2, setzt gccgo ebenfalls auf das gleiche Ergebnis, funktioniert jedoch länger als der reguläre Go-Compiler (1.10.4). Anscheinend läuft die Anwendung aufgrund der Tatsache, dass der reguläre Compiler den Start von Threads perfekt optimiert (in meinem Fall wurden 5 zusätzliche Threads auf 4 Kernen erstellt), schneller.

Fazit



Ich habe es immer noch geschafft, den Standard-Go-Compiler dazu zu bringen, mit sse-Anweisungen für die Schleife zu arbeiten, indem ich ihn nativ in sse float verschoben habe.

 package main // +build amd64 import "fmt" func main() { var i float64 = 0 var j float64 = 0 for i = 10000000; i>0; i-- { j += i } fmt.Println(j) } 


0x0000000000484bbe <+46>: movsd 0x4252a(%rip),%xmm3 # 0x4c70f0 <$f64.3ff0000000000000>
0x0000000000484bc6 <+54>: movups %xmm0,%xmm4
0x0000000000484bc9 <+57>: subsd %xmm3,%xmm0
0x0000000000484bcd <+61>: addsd %xmm4,%xmm1
0x0000000000484bd1 <+65>: xorps %xmm2,%xmm2
0x0000000000484bd4 <+68>: ucomisd %xmm2,%xmm0
0x0000000000484bd8 <+72>: ja 0x484bbe <main.main+46>

Source: https://habr.com/ru/post/de432986/


All Articles