sexta-feira, 27 de fevereiro de 2015

GCC -march e -mtune

As duas opções servem para otimizar a geração de código de máquina para determinada família de processadores. -march diz ao compilador qual conjunto de instruções será usado. -mtune mexe na forma como o código será organizado para tentar manter o processador o mais ocupado possível, escolhe instruções mais adequadas àquela família e aplica outras técnicas similares, porém sem modificar o conjunto base de instruções definido em -march.

O mínimo -march em 64-bit é x86-64, que é uma mescla de nocona (Pentium 4 e Xeon derivados) e k8 (AMD Athlon 64 e Opteron). Comparado com o mínimo i386 de 32-bit, é um ponto de corte bem mais alto, que oferece ao compilador mais instruções. Hoje em dia as distribuições configuram seus toolchains para usarem por padrão i686 (Pentium Pro para cima) em 32-bit e x86-64 em 64-bit. Escolhas de certa forma conservadoras, mas necessárias, pois estamos falando do conjunto de instruções.

Existe um -march especial: native. -march=native escolhe pelo CPU ID a família exata do processador em uso na máquina onde o GCC estiver rodando. Veja aí o que seria configurado com:

$ gcc -march=native -E -v - </dev/null 2>&1 | sed -n 's/.* -v - //p'

-mtune suporta as mesmas opções de -march, com a adição de uma especial: generic. Essa é diferente pois varia com o tempo a cada nova versão do compilador. O GCC promete com -mtune=generic otimizar para famílias de processadores atuais. No GCC 4.9, seus desenvolvedores consideram tais famílias as arquiteturas Nehalem (Intel) e Bobcat (AMD). É o -mtune padrão das distribuições.

[Atualização - 02/03/2015] Faltou comentar um segundo -mtune especial, suportado desde a versão 4.9: intel. -mtune=intel é similar ao generic; leva em consideração, contudo, apenas processadores da marca (arquiteturas Haswell e Silvermont no momento).

Cuidado com -march. Quando o processador não suportar alguma instrução, o programa será terminado com erro "instrução ilegal". O mesmo não acontece com -mtune porque não afeta o conjunto de instruções.

Esta é a diferença de um mesmo código compilado em 64-bit com -march=x86-64 e -march=sandybridge:

--- x86-64.s    2015-02-27 08:09:00.120616635 -0300
+++ sandybridge.s       2015-02-27 08:09:12.685131480 -0300
@@ -34,7 +34,7 @@
        call    clock_gettime
        imul    rcx, QWORD PTR [rsp+32], 1000000000
        imul    rdx, QWORD PTR [rsp+16], 1000000000
-       pxor    xmm0, xmm0
+       vxorpd  xmm0, xmm0, xmm0
        mov     esi, ebx
        mov     edi, OFFSET FLAT:.LC1
        add     ebx, 1
@@ -42,9 +42,9 @@
        add     rdx, QWORD PTR [rsp+24]
        mov     rax, rcx
        sub     rax, rdx
-       cvtsi2sdq       xmm0, rax
+       vcvtsi2sdq      xmm0, xmm0, rax
+       vdivsd  xmm0, xmm0, QWORD PTR .LC0[rip]
        mov     eax, 1
-       divsd   xmm0, QWORD PTR .LC0[rip]
        call    printf
        cmp     ebx, 6
        jne     .L2

Instruções SSE foram substituídas por equivalentes AVX. Ao rodar o binário num processador sem AVX:

$ ./sandybridge
Instrução ilegal (imagem do núcleo gravada)

$ journalctl -n2 -o cat
traps: sandybridge[11853] trap invalid opcode ip:400555 sp:7fff552ac870 error:0 in sandybridge[400000+1000]
Process 11853 (sandybridge) of user 1000 dumped core.

Stack trace of thread 11853:
#0  0x0000000000400555 main (sandybridge)
#1  0x00007fae5550cfe0 __libc_start_main (libc.so.6)
#2  0x00000000004005c0 _start (sandybridge)

Nenhum comentário:

Postar um comentário