Boucles C vs Go et mathématiques simples

Quand je me suis lassé de la programmation en C, comme beaucoup, j'étais intéressé par Go. Il est strictement typé, compilé, donc assez productif. Et puis j'ai voulu savoir à quel point les créateurs de Go étaient confus quant à l'optimisation de leur travail avec les boucles et les nombres.

Pour commencer, nous regardons comment va C.

Nous écrivons un code si simple:

#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; } 

Compiler avec O2, démonter:

 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> 

Nous obtenons le temps d'exécution:

réel 0m0,023s
utilisateur 0m0,019s
sys 0m0,004s

Il semblerait qu'il n'y ait nulle part où accélérer, mais nous avons un processeur moderne, pour de telles opérations, nous avons des registres sse rapides. Nous essayons les options gcc -mfpmath = sse -msse4.2 le résultat est le même.
Ajoutez -O3 et applaudissez:

  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> 

On peut voir que les commandes SSE2 et les registres SSE sont utilisés, et nous obtenons une triple augmentation des performances:

réel 0m0,006s
utilisateur 0m0,006s
sys 0m0,000s

Aussi sur Go:

 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 Go:
aller régulièrement:
réel 0m0,021s
utilisateur 0m0,018s
sys 0m0,004s

gccgo:
réel 0m0,058s
utilisateur 0m0,036s
sys 0m0,014s

Les performances, comme dans le cas de C et O2, donnent également à gccgo le même résultat, mais cela fonctionne plus longtemps que le compilateur Go (1.10.4) normal. Apparemment du fait que le compilateur régulier optimise parfaitement le lancement des threads (dans mon cas, 5 threads supplémentaires ont été créés sur 4 cœurs), l'application s'exécute plus rapidement.

Conclusion



J'ai quand même réussi à faire fonctionner le compilateur Go standard avec les instructions sse de la boucle en le glissant nativement dans sse float.

 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/fr432986/


All Articles