2013-05-17 24 views
5

ho tre funzioni a(), b() e c() che dovrebbero fare la stessa cosa:Qual è la differenza tra i tipi di vettorizzazione e gli array C incorporati in GCC?

typedef float Builtin __attribute__ ((vector_size (16))); 

typedef struct { 
     float values[4]; 
} Struct; 

typedef union { 
     Builtin b; 
     Struct s; 
} Union; 

extern void printv(Builtin); 
extern void printv(Union); 
extern void printv(Struct); 

int a() { 
     Builtin m = { 1.0, 2.0, 3.0, 4.0 }; 
     printv(m); 
} 

int b() { 
     Union m = { 1.0, 2.0, 3.0, 4.0 }; 
     printv(m); 
} 

int c() { 
     Struct m = { 1.0, 2.0, 3.0, 4.0 }; 
     printv(m); 
} 

Quando compilo questo codice osservo il seguente comportamento:

  • Quando si chiama printv() in a() tutto 4 galleggianti vengono passati da %xmm0. Non si verificano scritture sulla memoria.
  • Quando si chiama printv() in b() 2 float vengono passati da %xmm0 e gli altri due float da %xmm1. Per fare ciò, 4 float vengono caricati (.LC0) a %xmm2 e da lì alla memoria. Dopodiché, 2 float vengono letti dallo stesso posto in memoria a %xmm0 e gli altri 2 float vengono caricati (.LC1) a %xmm1.
  • Sono un po 'perso su ciò che lo fa c().

Perché a(), b() e c() sono diversi?

Ecco l'output assemblaggio di un():

 vmovaps .LC0(%rip), %xmm0 
     call _Z6printvU8__vectorf 

Il gruppo di uscita per b():

 vmovaps .LC0(%rip), %xmm2 
     vmovaps %xmm2, (%rsp) 
     vmovq .LC1(%rip), %xmm1 
     vmovq (%rsp), %xmm0 
     call _Z6printv5Union 

E l'uscita assemblaggio per c():

  andq $-32, %rsp 
     subq $32, %rsp 
     vmovaps .LC0(%rip), %xmm0 
     vmovaps %xmm0, (%rsp) 
     vmovq .LC2(%rip), %xmm0 
     vmovq 8(%rsp), %xmm1 
     call _Z6printv6Struct 

I dati:

 .section  .rodata.cst16,"aM",@progbits,16 
     .align 16 
.LC0: 
     .long 1065353216 
     .long 1073741824 
     .long 1077936128 
     .long 1082130432 
     .section  .rodata.cst8,"aM",@progbits,8 
     .align 8 
.LC1: 
     .quad 4647714816524288000 
     .align 8 
.LC2: 
     .quad 4611686019492741120 

Il quad 4647714816524288000 sembra non essere altro che i galleggianti 3.0 e 4.0 in parole adiacenti.

+1

Sembra che questo sia un problema con la convenzione di chiamata. '__m128' può essere passato direttamente dal registro. Ma 'Packed' deve essere passato con la divisione dei parametri tra' xmm0' e 'xmm1'. In breve, la convenzione di chiamata probabilmente impedisce al compilatore di fare una tale ottimizzazione. – Mysticial

+0

Ho modificato la domanda un bel po 'da quando hai postato il tuo commento. Ho sostituito tutte le cose relative ad AVX con i tipi di GCC integrati che ho trovato nei file di intestazione per rendere la domanda molto più comprensibile. Ma penso che potresti avere ragione riguardo alle convenzioni di chiamata. Ma perché? –

risposta

0

Bella domanda, ho dovuto scavare un po 'perché non ho mai usato lo stesso SSE (in questo caso SSE2). In sostanza, le istruzioni vettoriali vengono utilizzate per operare sui valori multipli memorizzati in un registro, ad esempio i registri XMM (0-7). In C il tipo di dati float utilizza IEEE 754 e la sua lunghezza è quindi di 32 bit. L'uso di quattro float produrrà un vettore di lunghezza 128 bit che è esattamente la lunghezza dei registri XMM (0-7). Ora i registri forniti da SSE simile a questa:

SSE (avx-128):       |----------------|name: XMM0; size: 128bit 
SSE (avx-256):  |----------------|----------------|name: YMM0; size: 256bit 

Nel primo caso a() si utilizza la vettorializzazione SIMD con

typedef float Builtin __attribute__ ((vector_size (16))); 

che consente di spostare l'intero vettore in un colpo solo nel registro XMM0 . Ora nel tuo secondo caso b() usi un sindacato. Ma poiché non si carica .LC0 nell'unione con Union m.b = { 1.0, 2.0, 3.0, 4.0 };, i dati non vengono riconosciuti come vettorizzazione. Ciò comporta il seguente comportamento:

I dati da.LC0 viene caricato in XMM2 con:

vmovaps .LC0(%rip), %xmm2 

ma perché i dati possono essere interpretati come una struttura o come vettorizzazione i dati devono essere diviso in due blocchi 64 bit che dovrà comunque essere nei registri XMM (0-7) perché può essere trattato come una vettorizzazione, ma deve essere al massimo 64 bit per poter essere trasferito a un registro (che è solo a 64 bit di larghezza e si rovescia se 128bit è stato trasferito su di esso; i dati sono persi) perché i dati possono anche essere trattati come una struttura. Questo è fatto nel seguito.

La vettorizzazione in XMM2 è caricato nella memoria con

vmovaps %xmm2, (%rsp) 

adesso i 64 bit superiori del vettorizzazione (bit 64-127), cioè i galleggianti 3.0 e 4.0 vengono spostati (muove vmovq quadword Per esempio 64 bit) a XMM1 con

vmovq .LC1(%rip), %xmm1 

ed infine i 64 bit inferiori del vettorizzazione (bit 0-63) cioè i carri 1.0 e 2.0 vengono spostati dalla memoria al XMM0 con

vmovq (%rsp), %xmm0 

Ora si ha la parte superiore e la parte inferiore del vettore a 128 bit in registri XMM separati (0-7).

Ora nel caso c() Non sono abbastanza sicuro, ma qui va. Primo% RSP è allineato ad un indirizzo a 32 bit e quindi 32 byte sono sottratti per memorizzare i dati sulla pila (questo allineare ad un indirizzo a 32 bit nuovamente) questo è fatto con

 andq $-32, %rsp 
    subq $32, %rsp 

ora questa volta vettorizzazione è caricata in XMM0 e poi messo in pila con

 vmovaps .LC0(%rip), %xmm0 
    vmovaps %xmm0, (%rsp) 

ed infine i 64 bit superiori del vettorizzazione sono memorizzati in XMM0 ed i 64 bit inferiori sono memorizzate nel registro XMM1 con

 vmovq .LC2(%rip), %xmm0 
    vmovq 8(%rsp), %xmm1 

In tutti e tre i casi la vettorizzazione viene trattata in modo diverso. Spero che questo ti aiuti.

+0

Questo è praticamente ciò che ho osservato. Mi piacerebbe davvero sapere perché GCC tratta i tre casi in modo diverso. Non vedo davvero alcuna differenza, almeno semanticamente. –

+0

Si sottolinea che "deve essere al massimo 64 bit per poter essere trasferito su un registro". Perché? –

+0

Il tuo processore è a 64 bit, cioè i registri sono a 64 bit di larghezza, solo XMM0-7 sono più larghi. Penso che il caso (b) lo mostri abbastanza bene. I dati sono memorizzati in modo diverso dal caso (a) perché i dati, a partire dall'unione_, possono essere interpretati come una ** vettorizzazione ** o una ** struttura **. –

Problemi correlati