Come unire uno scalare in un vettore senza che il compilatore sprechi un’istruzione che azzeri gli elementi superiori? Limitazione del design negli intrinsechi di Intel?

Non ho in mente un particolare caso d’uso; Sto chiedendo se questo è davvero un difetto di progettazione / limitazione dell’intrinseca Intel o se mi manca qualcosa.

Se si desidera combinare un float scalare con un vettore esistente, non sembra esserci un modo per farlo senza l’azzeramento degli elementi alti o la trasmissione dello scalare in un vettore, usando Intel intrinsec. Non ho studiato le estensioni vettoriali native di GNU C e i builtin associati.

Questo non sarebbe male se l’extra intrinseco fosse ottimizzato, ma non con gcc (5.4 o 6.2). Non c’è nemmeno un modo carino di usare pmovzx o insertps come carichi, per il motivo che le loro intrinseche richiedono solo argomenti vettoriali. (E gcc non piega un carico scalare-> vettoriale nell’istruzione asm).

 __m128 replace_lower_two_elements(__m128 v, float x) { __m128 xv = _mm_set_ss(x); // WANTED: something else for this step, some compilers actually compile this to a separate insn return _mm_shuffle_ps(v, xv, 0); // lower 2 elements are both x, and the garbage is gone } 

gcc 5.3 -march = nehalem -O3 output, per abilitare SSE4.1 e sintonizzare per quella CPU Intel: (È ancora peggio senza SSE4.1; più istruzioni per azzerare gli elementi superiori).

  insertps xmm1, xmm1, 0xe # pointless zeroing of upper elements. shufps only reads the low element of xmm1 shufps xmm0, xmm1, 0 # The function *should* just compile to this. ret 

TL: DR: il resto di questa domanda è solo chiedendo se puoi effettivamente farlo in modo efficiente, e se no perché no.


clang’s shuffle-optimizer funziona correttamente, e non spreca le istruzioni per azzerare gli elementi alti ( _mm_set_ss(x) ), o duplicare lo scalare in essi ( _mm_set1_ps(x) ). Invece di scrivere qualcosa che il compilatore deve ottimizzare, non dovrebbe esserci un modo per scriverlo “efficientemente” in C, in primo luogo? Anche il gcc molto recente non lo ottimizza, quindi questo è un problema reale (ma di minore importanza).


Ciò sarebbe ansible se esistesse un equivalente scalare -> 128b di __m256 _mm256_castps128_ps256 (__m128 a) . vale a dire produrre un __m128 con immondizia non definita negli elementi superiori e il float nell’elemento basso, compilando a zero istruzioni asm se il float / double scalare era già in un registro xmm.

Nessuno dei seguenti elementi intrinseci esiste, ma dovrebbero .

  • uno scalare -> __ equivalente _mm256_castps128_ps256 di _mm256_castps128_ps256 come descritto sopra. La soluzione più generale per il caso scalare già registrato.
  • __m128 _mm_move_ss_scalar (__m128 a, float s) : sostituisce l’elemento basso del vettore a con lo scalare s . Questo non è effettivamente necessario se esiste uno scalare generico -> __ m128 (punto elenco precedente). (La forma reg-reg di movss fonde, a differenza della forma di caricamento a cui si movd zeri e diversamente da movd quali zeri gli elementi superiori in entrambi i casi. Per copiare un registro con un float scalare senza false dipendenze, usa le movaps ).
  • __m128i _mm_loadzxbd (const uint8_t *four_bytes) e altre dimensioni di PMOVZX / PMOVSX: AFAICT, non c’è un modo sicuro per utilizzare gli intrinseci PMOVZX come carico , perché il modo scomodo e sicuro non ottimizza l’accesso a gcc.
  • __m128 _mm_insertload_ps (__m128 a, float *s, const int imm8) . INSERTPS si comporta diversamente come un carico: i 2 bit superiori di imm8 vengono ignorati e prende sempre lo scalare all’indirizzo effettivo (anziché un elemento di un vettore in memoria). Ciò consente di lavorare con indirizzi che non sono allineati a 16B e funzionano anche senza errori se il float proprio davanti a una pagina non mappata.

    Come con PMOVZX, gcc non riesce a piegare un _mm_load_ss() azzeramento dell’elemento superiore in un operando di memoria per INSERTPS. (Si noti che se i 2 bit superiori di imm8 non sono entrambi zero, allora _mm_insert_ps(xmm0, _mm_load_ss(), imm8) può compilare insertps xmm0,xmm0,foo , con un imm8 differente che zeri elementi in vec as-if l’elemento src era in realtà uno zero prodotto da MOVSS dalla memoria, in questo caso Clang utilizza XORPS / BLENDPS)


Esistono soluzioni alternative valide per emulare qualsiasi di quelle che sono entrambe sicure (non interrompere a -O0 ad esempio caricando 16B che potrebbe toccare la pagina successiva e segfault) ed efficiente (nessuna istruzione sprecata a -O3 con gcc e clang correnti almeno, preferibilmente anche altri importanti compilatori)? Preferibilmente anche in un modo leggibile, ma se necessario potrebbe essere messo dietro una funzione di wrapper in linea come __m128 float_to_vec(float a){ something(a); } __m128 float_to_vec(float a){ something(a); } .

C’è una buona ragione per Intel di non introdurre intrinseche come quelle? Avrebbero potuto aggiungere un float -> __ m128 con elementi superiori indefiniti allo stesso tempo di aggiungere _mm256_castps128_ps256 . È una questione di interni del compilatore che rende difficile l’implementazione? Forse in particolare gli interni di ICC?


Le principali convenzioni di chiamata su x86-64 (SysV o MS __vectorcall ) prendono il primo argomento FP in xmm0 e restituiscono gli argomenti FP scalari in xmm0, con gli elementi superiori non definiti. (Vedi il tag wiki x86 per i documenti ABI). Ciò significa che non è raro che il compilatore abbia un float / doppio scalare in un registro con elementi superiori sconosciuti. Questo sarà raro in un ciclo interno vettoriale, quindi penso che evitare queste inutili istruzioni per lo più risparmierà un po ‘di dimensioni del codice.

Il caso pmovzx è più serio: è qualcosa che potresti usare in un loop interno (ad es. Per una LUT delle maschere shuffle VPERMD, salvando un fattore 4 nell’impronta cache e memorizzando ogni indice riempito a 32 bit in memoria).


Il problema di pmovzx-as-a-load mi ha infastidito da un po ‘di tempo, e la versione originale di questa domanda mi ha fatto riflettere sul problema relativo all’utilizzo di un float scalare in un registro xmm. Ci sono probabilmente più casi d’uso per pmovzx come carico che per scalare -> __ m128.