c++ - despachador de CPU para Visual Studio para AVX y SSE
visual-studio (3)
Coloque las funciones SSE y AVX en diferentes archivos CPP y asegúrese de compilar la versión SSE sin /arch:AVX
.
Yo trabajo con dos computadoras. Uno sin soporte AVX y otro con AVX. Sería conveniente que mi código encuentre el conjunto de instrucciones admitido por mi CPU en tiempo de ejecución y elija la ruta de código adecuada. He seguido las sugerencias de Agner Fog para crear un despachador de CPU ( http://www.agner.org/optimize/#vectorclass ). Sin embargo, en mi mecanizado, sin la compilación AVX y la vinculación con Visual Studio, el código con AVX habilitado hace que el código se bloquee cuando lo ejecuto.
Quiero decir, por ejemplo, tengo dos archivos fuente uno con el conjunto de instrucciones SSE2 definido con algunas instrucciones SSE2 y otro con el conjunto de instrucciones AVX definido y con algunas instrucciones AVX. En mi función principal, si solo hago referencia a las funciones de SSE2, el código aún se cuelga en virtud de tener cualquier código fuente con AVX habilitado y con instrucciones de AVX. ¿Alguna pista sobre cómo puedo solucionar esto?
Editar: Bien, creo que he aislado el problema. Estoy usando la clase vectorial de Agner Fog y he definido tres archivos fuente como:
//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 <stdio.h>
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);
}
Esto se cuelga Si, en cambio, uso Vec4f en func_SSE2, no se bloquea. No entiendo esto. Puedo usar Vec8f con SSE2 solo, siempre que no tenga otro archivo fuente con AVX. El manual de Agner Fog dice
"No hay ninguna ventaja al usar las clases de vectores de coma flotante de 256 bits (Vec8f, Vec4d) a menos que se especifique el conjunto de instrucciones AVX, pero puede ser conveniente usar estas clases de todos modos si se usa el mismo código fuente con y sin AVX. Cada vector de 256 bits simplemente se dividirá en dos vectores de 128 bits cuando se compile sin AVX ".
Sin embargo, cuando tengo dos archivos de origen con Vec8f uno compilado con SSE2 y uno compilado con AVX, recibo un bloqueo.
Edit2: puedo hacer que funcione desde la línea de comando
>cl -c sse2.cpp
>cl -c /arch:AVX avx.cpp
>cl foo.cpp sse2.obj avx.obj
>foo.exe
Edit3: Esto, sin embargo, falla
>cl -c sse2.cpp
>cl -c /arch:AVX avx.cpp
>cl foo.cpp avx.obj sse2.obj
>foo.exe
Otra pista. Aparentemente, el orden de vincular importa. Se bloquea si avx.obj está antes de sse2.obj pero si sse2.obj está antes de avx.obj no se bloquea. No estoy seguro de si elige la ruta correcta del código (no tengo acceso a mi sistema AVX en este momento) pero al menos no falla.
El hecho de que la orden de enlace importe me hace pensar que podría haber algún tipo de código de inicialización en el archivo obj. Si el código de inicialización es comunal, solo se toma el primero. No puedo reproducirlo, pero debería poder verlo en una lista de ensamblaje (compilar con / c / Ftestavx.asm)
Me doy cuenta de que esta es una vieja pregunta y que la persona que la hizo parece que ya no está, pero acepté el mismo problema ayer. Esto es lo que resolví.
Cuando se compilan, sus archivos sse2.cpp y avx.cpp producen archivos de objetos que no solo contienen su función, sino también cualquier función de plantilla requerida. (por ejemplo, Vec8f::load
) Estas funciones de plantilla también se compilan utilizando el conjunto de instrucciones solicitado.
Esto significa que sus archivos de objeto sse2.obj y avx.obj contendrán definiciones de Vec8f::load
compiladas utilizando los conjuntos de instrucciones respectivos.
Sin embargo, dado que el compilador trata a Vec8f::load
como externamente visible, pone una sección ''COMDAT'' del archivo objeto con una etiqueta ''selectany'' (también conocida como ''pick any''). Esto le dice al vinculador que si ve múltiples definiciones de este símbolo, por ejemplo en 2 archivos de objeto diferentes, entonces se le permite elegir el que quiera. (Hace esto para reducir el código duplicado en el ejecutable final que de lo contrario sería inflado en tamaño por múltiples definiciones de plantilla y funciones en línea.)
El problema que está teniendo está directamente relacionado con esto, ya que el orden de los archivos objeto pasados al enlazador está afectando a cuál escoge. Específicamente aquí, parece estar eligiendo la primera definición que ve.
Si esto fue avx.obj, la versión compilada AVX de Vec8F::load
siempre será utilizada. Esto se bloqueará en una máquina que no admite ese conjunto de instrucciones. Por otro lado, si sse2.obj es el primero, se usará siempre la versión compilada SSE2. Esto no se bloqueará, pero solo usará las instrucciones SSE2 aunque AVX sea compatible.
Que este es el caso se puede ver si observa el archivo de enlace ''salida de archivo'' (producido utilizando la opción / mapa). Aquí están los extractos relevantes (editados):
//
// link with sse2.obj before avx.obj
//
0001:00000080 _main foo.obj
0001:00000330 func_sse2@@YAMPBM@Z sse2.obj
0001:00000420 ??0Vec256fe@@QAE@XZ sse2.obj
0001:00000440 ??0Vec4f@@QAE@ABT__m128@@@Z sse2.obj
0001:00000470 ??0Vec8f@@QAE@XZ sse2.obj <-- sse2 version used
0001:00000490 ??BVec4f@@QBE?AT__m128@@XZ sse2.obj
0001:000004c0 ?get_high@Vec8f@@QBE?AVVec4f@@XZ sse2.obj
0001:000004f0 ?get_low@Vec8f@@QBE?AVVec4f@@XZ sse2.obj
0001:00000520 ?load@Vec8f@@QAEAAV1@PBM@Z sse2.obj <-- sse2 version used
0001:00000680 ?func_avx@@YAMPBM@Z avx.obj
0001:00000740 ??BVec8f@@QBE?AT__m256@@XZ avx.obj
//
// link with avx.obj before sse2.obj
//
0001:00000080 _main foo.obj
0001:00000270 ?func_avx@@YAMPBM@Z avx.obj
0001:00000330 ??0Vec8f@@QAE@XZ avx.obj <-- avx version used
0001:00000350 ??BVec8f@@QBE?AT__m256@@XZ avx.obj
0001:00000380 ?load@Vec8f@@QAEAAV1@PBM@Z avx.obj <-- avx version used
0001:00000580 ?func_sse2@@YAMPBM@Z sse2.obj
0001:00000670 ??0Vec256fe@@QAE@XZ sse2.obj
0001:00000690 ??0Vec4f@@QAE@ABT__m128@@@Z sse2.obj
0001:000006c0 ??BVec4f@@QBE?AT__m128@@XZ sse2.obj
0001:000006f0 ?get_high@Vec8f@@QBE?AVVec4f@@XZ sse2.obj
0001:00000720 ?get_low@Vec8f@@QBE?AVVec4f@@XZ sse2.obj
En cuanto a arreglarlo, ese es otro asunto. En este caso, el siguiente truco directo debería funcionar forzando a la versión de avx a tener sus propias versiones con nombre diferente de las funciones de plantilla. Esto aumentará el tamaño del ejecutable resultante, ya que contendrá múltiples versiones de la misma función, incluso si las versiones sse2 y avx son idénticas.
// avx.cpp
namespace AVXWrapper {
/#include "vectorclass.h"
}
using namespace AVXWrapper;
float func_avx(const float* a)
{
...
}
Sin embargo, existen algunas limitaciones importantes: (a) si el archivo incluido gestiona cualquier forma de estado global, ya no será verdaderamente global ya que tendrá 2 versiones "semi-globales", y (b) no podrá pasar variables de clase de vector como parámetros entre otro código y funciones definidas en avx.cpp.