Диспетчер процессора для Visual Studio для AVX и SSE

Я работаю с двумя компьютерами. Один без поддержки AVX и один с AVX. Было бы удобно, чтобы мой код находил набор команд, поддерживаемый моим ЦП, во время выполнения и выбирал подходящий путь к коду.
Я следую советам Агнера Фога по созданию диспетчера процессора (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);
}

Это вылетает. Если я вместо этого использую Vec4f в func_SSE2, он не падает. Я не понимаю этого. Я могу использовать Vec8f с SSE2 отдельно, если у меня нет другого исходного файла с AVX. Руководство Агнера Фога гласит

«Нет никакого преимущества в использовании 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), но по крайней мере он не падает.

8

Решение

Я понимаю, что это старый вопрос, и человека, который его задал, похоже, больше нет, но я вчера столкнулся с той же проблемой. Вот что я разработал.

После компиляции ваши файлы sse2.cpp и avx.cpp создают объектные файлы, которые содержат не только вашу функцию, но и все необходимые функции шаблона.
(например. Vec8f::load) Эти функции шаблона также компилируются с использованием запрошенного набора инструкций.

Означает, что ваши объектные файлы sse2.obj и avx.obj будут содержать определения Vec8f::load каждый скомпилирован с использованием соответствующих наборов инструкций.

Тем не менее, так как компилятор лечит Vec8f::load как внешне видимый, он помещает его в раздел «COMDAT» объектного файла с меткой «selectany» (также называемый «выбрать любой»). Это говорит компоновщику, что если он видит несколько определений этого символа, например, в 2 разных объектных файлах, то ему разрешается выбирать любой, который ему нравится. (Это делается для уменьшения дублирующегося кода в конечном исполняемом файле, который в противном случае был бы увеличен по размеру несколькими определениями шаблонных и встроенных функций.)

Проблема, с которой вы столкнулись, напрямую связана с тем, что порядок объектных файлов, передаваемых компоновщику, влияет на то, какой из них он выбирает. В частности, здесь, похоже, выбирается первое определение, которое он видит.

Если это был avx.obj, то скомпилированная версия AVX Vec8F::load всегда будет использоваться. Это приведет к сбою на машине, которая не поддерживает этот набор инструкций.
С другой стороны, если sse2.obj является первым, то всегда будет использоваться скомпилированная версия SSE2. Это не приведет к сбою, но будет использовать только инструкции SSE2, даже если поддерживается AVX.

То, что это так, можно увидеть, если вы посмотрите на вывод файла компоновщика ‘map’ (созданный с использованием параметра / 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 «полуглобальные» версии, и
(б) вы не сможете передавать переменные векторного класса в качестве параметров между другим кодом и функциями, определенными в avx.cpp.

7

Другие решения

Тот факт, что порядок ссылок имеет значение, заставляет меня думать, что в файле obj может быть какой-то код инициализации. Если код инициализации является общим, то берется только первый. Я не могу воспроизвести его, но вы должны увидеть его в списке сборки (скомпилируйте с / c /Ftestavx.asm)

2

Поместите функции SSE и AVX в разные файлы CPP и обязательно скомпилируйте версию SSE без /arch:AVX,

1