Dispatcher della cpu per Visual Studio per AVX e SSE

Lavoro con due computer. Uno senza supporto AVX e uno con AVX. Sarebbe utile che il mio codice trovi il set di istruzioni supportato dalla mia CPU in fase di esecuzione e scelga il percorso del codice appropriato. Ho seguito i suggerimenti di Agner Fog per creare un dispatcher della CPU ( http://www.agner.org/optimize/#vectorclass ). Tuttavia, sul mio maching senza la compilazione AVX e il collegamento con Visual Studio, il codice con AVX triggersto causa il crash del codice durante l’esecuzione.

Intendo ad esempio che ho due file sorgente uno con il set di istruzioni SSE2 definito con alcune istruzioni SSE2 e un altro con il set di istruzioni AVX definito e con alcune istruzioni AVX. Nella mia funzione principale, se faccio riferimento solo alle funzioni SSE2, il codice si blocca ancora in virtù dell’utilizzo di qualsiasi codice sorgente con AVX abilitato e con le istruzioni AVX. Qualche indizio su come posso risolvere questo problema?

Edit: Ok, penso di aver isolato il problema. Sto usando la class vettoriale di Agner Fog e ho definito tre file sorgente come:

//file sse2.cpp - compiled with /arch:SSE2 #include "vectorclass.h" float func_sse2(const float* a) { Vec8f v1 = Vec8f().load(a); float sum = horizontal_add(v1); return sum; } //file avx.cpp - compiled with /arch:AVX #include "vectorclass.h" float func_avx(const float* a) { Vec8f v1 = Vec8f().load(a); float sum = horizontal_add(v1); return sum; } //file foo.cpp - compiled with /arch:SSE2 #include  extern float func_sse2(const float* a); extern float func_avx(const float* a); int main() { float (*fp)(const float*a); float a[] = {1,2,3,4,5,6,7,8}; int iset = 6; if(iset>=7) { fp = func_avx; } else { fp = func_sse2; } float sum = (*fp)(a); printf("sum %f\n", sum); } 

Questo si blocca. Se invece utilizzo Vec4f in func_SSE2 non si blocca. Non lo capisco Posso usare Vec8f con SSE2 da solo fino a quando non ho un altro file sorgente con AVX. Il manuale di Agner Fog dice

“Non vi è alcun vantaggio nell’uso delle classi di vettori in virgola mobile a 256 bit (Vec8f, Vec4d) a meno che non sia specificato il set di istruzioni AVX, ma può essere conveniente usare comunque queste classi se lo stesso codice sorgente viene utilizzato con e senza AVX. Ogni vettore a 256 bit verrà semplicemente diviso in due vettori a 128 bit durante la compilazione senza AVX. ”

Tuttavia, quando ho due file sorgente con Vec8f uno compilato con SSE2 e uno compilato con AVX, ho un crash.

Edit2: posso farlo funzionare dalla riga di comando

 >cl -c sse2.cpp >cl -c /arch:AVX avx.cpp >cl foo.cpp sse2.obj avx.obj >foo.exe 

Edit3: questo, tuttavia, si blocca

 >cl -c sse2.cpp >cl -c /arch:AVX avx.cpp >cl foo.cpp avx.obj sse2.obj >foo.exe 

Un altro indizio Apparentemente, l’ordine di collegamento conta. Crolla se avx.obj è prima di sse2.obj ma se sse2.obj è prima di avx.obj non si blocca. Non sono sicuro che scelga il percorso corretto del codice (non ho accesso al mio sistema AVX in questo momento) ma almeno non si blocca.

Mi rendo conto che si tratta di una vecchia domanda e che la persona che ha chiesto che sembra non essere più in giro, ma ho colpito lo stesso problema di ieri. Ecco cosa ho elaborato.

Quando vengono compilati, entrambi i file sse2.cpp e avx.cpp producono file object che contengono non solo la funzione ma anche eventuali funzioni del modello richieste. (ad es. Vec8f::load ) Queste funzioni template sono anche compilate usando il set di istruzioni richiesto.

Ciò significa che i file degli oggetti sse2.obj e avx.obj conterranno entrambe le definizioni di Vec8f::load ciascuna compilata utilizzando i rispettivi set di istruzioni.

Tuttavia, dal momento che il compilatore considera Vec8f::load come esternamente visibile, lo mette in una sezione ‘COMDAT’ del file object con un’etichetta ‘selectany’ (aka ‘pick any’). Questo dice al linker che se vede più definizioni di questo simbolo, ad esempio in 2 file object diversi, allora è permesso di sceglierne uno a piacere. (Lo fa per ridurre il codice duplicato nell’eseguibile finale che altrimenti sarebbe gonfiato in termini di dimensioni da più definizioni di template e funzioni inline.)

Il problema riscontrato è direttamente correlato a questo in quanto l’ordine dei file object passati al linker influisce su quale seleziona. Nello specifico, sembra che stia selezionando la prima definizione che vede.

Se fosse avx.obj, verrà sempre utilizzata la versione compilata AVX di Vec8F::load . Questo si bloccherà su una macchina che non supporta quel set di istruzioni. D’altra parte se sse2.obj è il primo, verrà sempre utilizzata la versione compilata SSE2. Questo non si blocca ma utilizzerà le istruzioni SSE2 anche se AVX è supportato.

Che questo è il caso può essere visto se si guarda l’output del file ‘mappa’ del linker (prodotto utilizzando l’opzione / map.) Ecco gli estratti rilevanti (modificati) –

 // // link with sse2.obj before avx.obj // 0001:00000080 _main foo.obj 0001:00000330 [email protected]@[email protected] sse2.obj 0001:00000420 [email protected]@[email protected] sse2.obj 0001:00000440 [email protected]@[email protected][email protected]@@Z sse2.obj 0001:00000470 [email protected]@[email protected] sse2.obj <-- sse2 version used 0001:00000490 [email protected]@[email protected]@XZ sse2.obj 0001:000004c0 [email protected]@@[email protected]@XZ sse2.obj 0001:000004f0 [email protected]@@[email protected]@XZ sse2.obj 0001:00000520 [email protected]@@[email protected]@Z sse2.obj <-- sse2 version used 0001:00000680 [email protected]@[email protected] avx.obj 0001:00000740 [email protected]@[email protected]@XZ avx.obj // // link with avx.obj before sse2.obj // 0001:00000080 _main foo.obj 0001:00000270 [email protected]@[email protected] avx.obj 0001:00000330 [email protected]@[email protected] avx.obj <-- avx version used 0001:00000350 [email protected]@[email protected]@XZ avx.obj 0001:00000380 [email protected]@@[email protected]@Z avx.obj <-- avx version used 0001:00000580 [email protected]@[email protected] sse2.obj 0001:00000670 [email protected]@[email protected] sse2.obj 0001:00000690 [email protected]@[email protected][email protected]@@Z sse2.obj 0001:000006c0 [email protected]@[email protected]@XZ sse2.obj 0001:000006f0 [email protected]@@[email protected]@XZ sse2.obj 0001:00000720 [email protected]@@[email protected]@XZ sse2.obj 

Per quanto riguarda ripararlo, questa è un'altra questione. In questo caso, il seguente smembramento dovrebbe funzionare forzando la versione avx ad avere le sue versioni con nomi diversi delle funzioni del template. Ciò aumenterà la dimensione eseguibile risultante in quanto conterrà più versioni della stessa funzione anche se le versioni sse2 e avx sono identiche.

 // avx.cpp namespace AVXWrapper { \#include "vectorclass.h" } using namespace AVXWrapper; float func_avx(const float* a) { ... } 

Esistono tuttavia alcune limitazioni importanti: (a) se il file incluso gestisce qualsiasi forma di stato globale, non sarà più realmente globale poiché si avranno 2 versioni 'semi-globali', e (b) non si sarà in grado di passare le variabili della class vettoriale come parametri tra altro codice e funzioni definite in avx.cpp.

Il fatto che l’ordine di collegamento sia importante mi fa pensare che potrebbe esserci qualche tipo di codice di inizializzazione nel file obj. Se il codice di inizializzazione è comune, viene preso solo il primo. Non riesco a riprodurlo, ma dovresti essere in grado di vederlo in un elenco di assembly (compile con / c /Ftestavx.asm)

Inserisci le funzioni SSE e AVX in diversi file CPP e assicurati di compilare la versione SSE wihout /arch:AVX .