float to double conversion:perché così tante istruzioni?

float to double conversion:perché così tante istruzioni?


Sono curioso di sapere se qualcuno può far luce su questo per me. Sto lavorando su alcune cose di conversione di dati numerici e ho diverse funzioni che eseguono conversioni di dati, che definisco utilizzando due macro:


#define CONV_VIA_CAST(name, dtype, vtype)                               \
static inline void name(void *data, void *view, size_t len) { \
vtype *vptr = (vtype*)view; \
dtype *dptr = (dtype*)data; \
for (size_t ii=0; ii < len/sizeof(vtype); ii++) { \
*vptr++ = (vtype)*dptr++; \
} \
}
#define CONV_VIA_FUNC(name, dtype, vtype, via) \
static inline void name(void *data, void *view, size_t len) { \
vtype *vptr = (vtype*)view; \
dtype *dptr = (dtype*)data; \
for (size_t ii=0; ii < len/sizeof(vtype); ii++) { \
*vptr++ = (vtype)via(*dptr++); \
} \
}

Quando definisco una conversione da float a int:


 CONV_VIA_FUNC(f_to_i, float, int16_t, lrintf); 

Ottengo un bel pezzo di assemblaggio conciso con -O3 su:


   0x0000000000401fb0 <+0>:     shr    %rdx
0x0000000000401fb3 <+3>: je 0x401fd3 <f_to_i+35>
0x0000000000401fb5 <+5>: xor %eax,%eax
0x0000000000401fb7 <+7>: nopw 0x0(%rax,%rax,1)
0x0000000000401fc0 <+16>: cvtss2si (%rdi,%rax,4),%rcx
0x0000000000401fc6 <+22>: mov %cx,(%rsi,%rax,2)
0x0000000000401fca <+26>: add $0x1,%rax
0x0000000000401fce <+30>: cmp %rdx,%rax
0x0000000000401fd1 <+33>: jne 0x401fc0 <f_to_i+16>
0x0000000000401fd3 <+35>: repz retq

Tuttavia, quando definisco una funzione float->double (o double->float):


CONV_VIA_CAST(f_to_d, float,   double); 

Ho questa mostruosità:


   0x0000000000402040 <+0>:     mov    %rdx,%r8
0x0000000000402043 <+3>: shr $0x3,%r8
0x0000000000402047 <+7>: test %r8,%r8
0x000000000040204a <+10>: je 0x402106 <f_to_d+198>
0x0000000000402050 <+16>: shr $0x5,%rdx
0x0000000000402054 <+20>: lea 0x0(,%rdx,4),%r9
0x000000000040205c <+28>: test %r9,%r9
0x000000000040205f <+31>: je 0x402108 <f_to_d+200>
0x0000000000402065 <+37>: lea (%rdi,%r8,4),%rax
0x0000000000402069 <+41>: cmp $0xb,%r8
0x000000000040206d <+45>: lea (%rsi,%r8,8),%r10
0x0000000000402071 <+49>: seta %cl
0x0000000000402074 <+52>: cmp %rax,%rsi
0x0000000000402077 <+55>: seta %al
0x000000000040207a <+58>: cmp %r10,%rdi
0x000000000040207d <+61>: seta %r10b
0x0000000000402081 <+65>: or %r10d,%eax
0x0000000000402084 <+68>: test %al,%cl
0x0000000000402086 <+70>: je 0x402108 <f_to_d+200>
0x000000000040208c <+76>: xorps %xmm3,%xmm3
0x000000000040208f <+79>: xor %eax,%eax
0x0000000000402091 <+81>: xor %ecx,%ecx
0x0000000000402093 <+83>: nopl 0x0(%rax,%rax,1)
0x0000000000402098 <+88>: movaps %xmm3,%xmm0
0x000000000040209b <+91>: add $0x1,%rcx
0x000000000040209f <+95>: movlps (%rdi,%rax,1),%xmm0
0x00000000004020a3 <+99>: movhps 0x8(%rdi,%rax,1),%xmm0
0x00000000004020a8 <+104>: movhlps %xmm0,%xmm1
0x00000000004020ab <+107>: cvtps2pd %xmm0,%xmm2
0x00000000004020ae <+110>: cvtps2pd %xmm1,%xmm0
0x00000000004020b1 <+113>: movlpd %xmm2,(%rsi,%rax,2)
0x00000000004020b6 <+118>: movhpd %xmm2,0x8(%rsi,%rax,2)
0x00000000004020bc <+124>: movlpd %xmm0,0x10(%rsi,%rax,2)
0x00000000004020c2 <+130>: movhpd %xmm0,0x18(%rsi,%rax,2)
0x00000000004020c8 <+136>: add $0x10,%rax
0x00000000004020cc <+140>: cmp %rcx,%rdx
0x00000000004020cf <+143>: ja 0x402098 <f_to_d+88>
0x00000000004020d1 <+145>: cmp %r9,%r8
0x00000000004020d4 <+148>: lea (%rsi,%r9,8),%rsi
0x00000000004020d8 <+152>: lea (%rdi,%r9,4),%rdi
0x00000000004020dc <+156>: je 0x40210d <f_to_d+205>
0x00000000004020de <+158>: mov %r9,%rdx
0x00000000004020e1 <+161>: mov %r9,%rax
0x00000000004020e4 <+164>: neg %rdx
0x00000000004020e7 <+167>: lea (%rsi,%rdx,8),%rcx
0x00000000004020eb <+171>: lea (%rdi,%rdx,4),%rdx
0x00000000004020ef <+175>: nop
0x00000000004020f0 <+176>: movss (%rdx,%rax,4),%xmm0
0x00000000004020f5 <+181>: cvtps2pd %xmm0,%xmm0
0x00000000004020f8 <+184>: movsd %xmm0,(%rcx,%rax,8)
0x00000000004020fd <+189>: add $0x1,%rax
0x0000000000402101 <+193>: cmp %rax,%r8
0x0000000000402104 <+196>: ja 0x4020f0 <f_to_d+176>
0x0000000000402106 <+198>: repz retq
0x0000000000402108 <+200>: xor %r9d,%r9d
0x000000000040210b <+203>: jmp 0x4020de <f_to_d+158>
0x000000000040210d <+205>: nopl (%rax)
0x0000000000402110 <+208>: retq

Qualcuno può far luce su cosa sta succedendo qui sotto il cofano per la conversione float->doppia? E forse come potrebbe essere scritto per ottenere un assemblaggio più efficiente? Sto usando gcc 4.6.3 se questo è importante.


Risposte:


Ci sono molte cose che accadono qui che posso vedere rapidamente (il codice è un po' lungo, il tempo è un po' tardi e non sono un fan della sintassi AT&T).


In primo luogo, il secondo ciclo è stato vettorizzato (ma male, vedi sotto). Ciò provoca intrinsecamente un po 'di codice gonfio:ora deve fare i conti con una "coda" che è più corta di un vettore e simili.


In secondo luogo, float to double è una conversione allargata. Non importa per gli scalari, ma con i vettori ciò significa che non puoi semplicemente leggere alcuni dati, convertirli e riscriverli - da qualche parte lungo le linee finirai con il doppio dei byte e devono essere gestiti insieme a. (da cui il movhlps %xmm0,%xmm1 )


Il ciclo effettivo si estende solo da 402098h a 4020cfh, sotto c'è la "gestione della coda", e sopra c'è una mostruosità che verifica se ha saltato completamente il ciclo principale e alcune cose che non ho ancora capito - renderebbe senso se fosse per l'allineamento, ma non vedo alcun test rdi, 15 c'è dentro, né qualcosa di ovvio che eliminerebbe un inizio non allineato.


E in terzo luogo, GCC è zoppo. Questo non è insolito. Sembra pensare che xmm3 sia in qualche modo coinvolto, cosa che non lo è, e sembra aver dimenticato che i vettori possono essere caricati su an dalla memoria in un unico pezzo - anche in questo caso ciò potrebbe essere dovuto al fatto che la mostruosità all'inizio davvero non prova per l'allineamento e questa è la sua difesa contro i puntatori non allineati. In ogni caso, però, GCC ha fatto un pessimo lavoro qui.