Я работаю с двумя компьютерами. Один без поддержки 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), но по крайней мере он не падает.
Я понимаю, что это старый вопрос, и человека, который его задал, похоже, больше нет, но я вчера столкнулся с той же проблемой. Вот что я разработал.
После компиляции ваши файлы 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.
Тот факт, что порядок ссылок имеет значение, заставляет меня думать, что в файле obj может быть какой-то код инициализации. Если код инициализации является общим, то берется только первый. Я не могу воспроизвести его, но вы должны увидеть его в списке сборки (скомпилируйте с / c /Ftestavx.asm)
Поместите функции SSE и AVX в разные файлы CPP и обязательно скомпилируйте версию SSE без /arch:AVX
,