我使用两台电脑。一个没有AVX支持,另一个没有AVX。让我的代码在运行时找到我的CPU支持的指令集并选择适当的代码路径会很方便。 我遵循Agner Fog的建议来制作一个CPU调度员(http://www.agner.org/optimize/#vectorclass)。但是,在我的机器上进行AVX编译并与visual studio链接时,启用AVX的代码会导致代码在运行时崩溃。
我的意思是例如我有两个源文件,其中一个SSE2指令集定义了一些SSE2指令,另一个源文件定义了AVX指令集和一些AVX指令。在我的主函数中,如果我只引用SSE2函数,代码仍会因启用了AVX和AVX指令的任何源代码而崩溃。我可以解决这个问题的任何线索吗?
编辑: 好的,我想我已经解决了这个问题。我正在使用Agner Fog的矢量类,我已经定义了三个源文件:
//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);
}
这次崩溃。如果我改为在func_SSE2中使用Vec4f,它不会崩溃。我不明白这一点。只要我没有AVX的另一个源文件,我可以单独使用Vec8f和SSE2。 Agner Fog的手册说
“使用256位浮点矢量类没有优势(Vec8f, Vec4d)除非指定了AVX指令集,否则使用起来会很方便 无论如何,如果使用和不使用AVX使用相同的源代码,这些类。 编译时,每个256位向量将简单地分成两个128位向量 没有AVX。“
但是,当我有两个使用Vec8f的源文件,一个用SSE2编译,一个用AVX编译,然后我就崩溃了。
EDIT2: 我可以从命令行开始工作
>cl -c sse2.cpp
>cl -c /arch:AVX avx.cpp
>cl foo.cpp sse2.obj avx.obj
>foo.exe
EDIT3: 但是,这会崩溃
>cl -c sse2.cpp
>cl -c /arch:AVX avx.cpp
>cl foo.cpp avx.obj sse2.obj
>foo.exe
另一个线索。显然,关联的顺序很重要。如果avx.obj在sse2.obj之前崩溃,但如果sse2.obj在avx.obj之前,它就不会崩溃。我不确定它是否选择了正确的代码路径(我现在无法访问我的AVX系统),但至少它不会崩溃。
答案 0 :(得分:7)
我意识到这是一个古老的问题,那个问过它的人似乎已经不在了,但我昨天遇到了同样的问题。这就是我的成果。
编译时,sse2.cpp和avx.cpp文件都会生成不仅包含函数的对象文件,还包含任何必需的模板函数。
(例如Vec8f::load
)这些模板函数也使用请求的指令集进行编译。
这意味着你的sse2.obj和avx.obj目标文件都将包含Vec8f::load
的定义,每个定义使用相应的指令集编译。
但是,由于编译器将Vec8f::load
视为外部可见,因此将其置于“COMDAT”状态。带有&#39; selectany&#39;的目标文件的一部分(又名&#39;选择任何&#39;)标签。这告诉链接器如果它看到该符号的多个定义,例如在2个不同的目标文件中,则允许它选择它喜欢的任何一个。 (这样做是为了减少最终可执行文件中的重复代码,否则会因模板和内联函数的多个定义而导致大小膨胀。)
您遇到的问题与此直接相关,因为传递给链接器的目标文件的顺序会影响它选择的对象。具体来说,它似乎是在选择它看到的第一个定义。
如果这是avx.obj,那么将始终使用AVX编译版本的Vec8F::load
。这将在不支持该指令集的机器上崩溃。
另一方面,如果sse2.obj是第一个,那么将始终使用SSE2编译版本。这不会崩溃,但即使支持AVX,它也只会使用SSE2指令。
如果你看一下链接器&#39; map&#39;就是这种情况。文件输出(使用/ map选项生成。)以下是相关(已编辑)的摘录 -
//
// 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
至于修复它,那是另一回事。在这种情况下,通过强制avx版本具有其自己的不同命名的模板函数版本,以下钝器应该起作用。这将增加生成的可执行文件大小,因为即使sse2和avx版本相同,它也将包含相同功能的多个版本。
// avx.cpp
namespace AVXWrapper {
\#include "vectorclass.h"
}
using namespace AVXWrapper;
float func_avx(const float* a)
{
...
}
但是有一些重要的限制 - (a)如果所包含的文件管理任何形式的全球国家,它将不再是真正的全球性,因为你将拥有2&#39;半全球状态&#39;版本,和 (b)您无法将vectorclass变量作为avx.cpp中定义的其他代码和函数之间的参数传递。
答案 1 :(得分:2)
链接顺序很重要的事实使我认为obj文件中可能存在某种初始化代码。如果初始化代码是公共的,则只采用第一个。我无法重现它,但你应该能够在汇编列表中看到它(用/ c /Ftestavx.asm编译)
答案 2 :(得分:1)
将SSE和AVX函数放在不同的CPP文件中,并确保编译SSE版本,而不是/arch:AVX
。