8

我用两台电脑工作。一种不支持 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。阿格纳雾的手册说

“除非指定了 AVX 指令集,否则使用 256 位浮点向量类(Vec8f、Vec4d)没有任何优势,但如果在有和没有 AVX 的情况下使用相同的源代码,则无论如何使用这些类会很方便。在没有 AVX 的情况下编译时,每个 256 位向量将简单地分成两个 128 位向量。”

但是,当我有两个使用 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 系统),但至少它不会崩溃。

4

3 回答 3

8

我意识到这是一个老问题,提出这个问题的人似乎已经不在了,但我昨天遇到了同样的问题。这是我的工作。

编译后,您的 sse2.cpp 和 avx.cpp 文件都会生成目标文件,其中不仅包含您的函数,还包含任何所需的模板函数。(例如Vec8f::load)这些模板函数也是使用请求的指令集编译的。

这意味着您的 sse2.obj 和 avx.obj 目标文件都将包含Vec8f::load使用各自指令集编译的每个定义。

但是,由于编译器将Vec8f::load其视为外部可见的,因此它会将其置于目标文件的“COMDAT”部分,并带有“selectany”(又名“pick any”)标签。这告诉链接器,如果它看到这个符号的多个定义,例如在 2 个不同的目标文件中,那么它可以选择它喜欢的任何一个。(这样做是为了减少最终可执行文件中的重复代码,否则这些重复代码会因模板和内联函数的多个定义而膨胀。)

您遇到的问题与此直接相关,因为传递给链接器的目标文件的顺序会影响它选择的文件。具体来说,它似乎选择了它看到的第一个定义。

如果这是 avx.obj,Vec8F::load则将始终使用 AVX 编译版本。这将在不支持该指令集的机器上崩溃。另一方面,如果首先使用 sse2.obj,则将始终使用 SSE2 编译版本。这不会崩溃,但即使支持 AVX,它也只会使用 SSE2 指令。

如果您查看链接器“映射”文件输出(使用 /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

至于修复它,那是另一回事。在这种情况下,以下直截了当的 hack 应该通过强制 avx 版本拥有自己不同命名的模板函数版本来工作。这将增加生成的可执行文件大小,因为即使 sse2 和 avx 版本相同,它也会包含同一函数的多个版本。

// avx.cpp
namespace AVXWrapper {
\#include "vectorclass.h"
}
using namespace AVXWrapper;

float func_avx(const float* a)
{
    ...
}

但是有一些重要的限制 - (a) 如果包含的文件管理任何形式的全局状态,它将不再是真正的全局状态,因为您将拥有 2 个“半全局”版本,并且 (b) 您将无法在 avx.cpp 中定义的其他代码和函数之间传递向量类变量作为参数。

于 2014-09-17T07:38:32.617 回答
2

链接顺序很重要的事实使我认为 obj 文件中可能存在某种初始化代码。如果初始化代码是公用的,则只采用第一个。我无法重现它,但您应该能够在程序集列表中看到它(使用 /c /Ftestavx.asm 编译)

于 2013-03-15T11:58:07.380 回答
1

将 SSE 和 AVX 函数放在不同的 CPP 文件中,并确保编译 SSE 版本 wihout /arch:AVX

于 2013-03-14T10:55:12.820 回答