Имеют разные оптимизации (простой, SSE, AVX) в одном и том же исполняемом файле с C / C ++

Я разрабатываю оптимизацию для своих 3D-расчетов, и теперь у меня есть:

  • « plain » версия с использованием стандартных библиотек языка C,
  • оптимизированная версия SSE которая компилируется с использованием препроцессора #define USE_SSE ,
  • оптимизированная версия AVX которая компилируется с использованием препроцессора #define USE_AVX

Можно ли переключаться между 3 версиями без необходимости компилировать разные исполняемые файлы (например, иметь разные файлы библиотек и загружать «правый» один динамически, не знаете, являются ли inline функции «правильными» для этого)? Я бы подумал и о том, что в этом программном обеспечении есть такой переключатель.

Для этого есть несколько решений.

Один из них основан на C ++, где вы создадите несколько classов – как правило, вы реализуете class интерфейса и используете фабричную функцию, чтобы дать вам объект правильного classа.

например

 class Matrix { virtual void Multiply(Matrix &result, Matrix& a, Matrix &b) = 0; ... }; class MatrixPlain : public Matrix { void Multiply(Matrix &result, Matrix& a, Matrix &b); }; void MatrixPlain::Multiply(...) { ... implementation goes here... } class MatrixSSE: public Matrix { void Multiply(Matrix &result, Matrix& a, Matrix &b); } void MatrixSSE::Multiply(...) { ... implementation goes here... } ... same thing for AVX... Matrix* factory() { switch(type_of_math) { case PlainMath: return new MatrixPlain; case SSEMath: return new MatrixSSE; case AVXMath: return new MatrixAVX; default: cerr << "Error, unknown type of math..." << endl; return NULL; } } 

Или, как было предложено выше, вы можете использовать общие библиотеки, которые имеют общий интерфейс, и динамически загружать нужную библиотеку.

Конечно, если вы реализуете базовый class Matrix как ваш «простой» class, вы можете сделать поэтапное уточнение и реализовать только те части, которые вы на самом деле находите, выгодны, и полагаться на базовый class для реализации функций, в которых производительность не очень критическая.

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

Я также рассмотрю, как вы храните свои данные. Вы храните наборы массива с X, Y, Z, W, или вы храните много X, много Y, много Z и много W в отдельных массивах [при условии, что мы делаем 3D-вычисления]? В зависимости от того, как работает ваш расчет, вы можете обнаружить, что выполнение одного или другого способа даст вам наилучшую выгоду.

Я сделал справедливую бит SSE и 3DNow! оптимизация несколько лет назад, и «трюк» часто больше связан с тем, как вы храните данные, чтобы вы могли легко захватить «комплект» правильного типа данных за один раз. Если у вас есть данные, которые хранятся неправильно, вы будете тратить много времени на «swizzling data» (перемещение данных с одного способа хранения на другой).

Один из способов – реализовать три библиотеки, соответствующие одному и тому же интерфейсу. С динамическими библиотеками вы можете просто поменять файл библиотеки, и исполняемый файл будет использовать все, что он найдет. Например, в Windows вы можете скомпилировать три библиотеки DLL:

  • PlainImpl.dll
  • SSEImpl.dll
  • AVXImpl.dll

А затем создайте исполняемую ссылку на Impl.dll . Теперь просто поместите одну из трех конкретных DLL в тот же каталог, что и .exe , переименуйте его в Impl.dll , и он будет использовать эту версию. Тот же принцип должен в принципе применяться к UNIX-подобной ОС.

Следующим шагом было бы программно загрузить библиотеки, что, вероятно, является наиболее гибким, но оно специфично для ОС и требует некоторой дополнительной работы (например, открытие библиотеки, получение указателей на функции и т. Д.),

Редактирование: Но, конечно, вы могли бы просто реализовать функцию три раза и выбрать ее во время выполнения, в зависимости от некоторых параметров / параметров файла конфигурации и т. Д., Как это было в других ответах.

Конечно, это возможно.

Лучший способ сделать это – иметь функции, которые выполняют всю работу, и выбирать среди них во время выполнения. Это будет работать, но не является оптимальным:

 typedef enum { calc_type_invalid = 0, calc_type_plain, calc_type_sse, calc_type_avx, calc_type_max // not a valid value } calc_type; void do_my_calculation(float const *input, float *output, size_t len, calc_type ct) { float f; size_t i; for (i = 0; i < len; ++i) { switch (ct) { case calc_type_plain: // plain calculation here break; case calc_type_sse: // SSE calculation here break; case calc_type_avx: // AVX calculation here break; default: fprintf(stderr, "internal error, unexpected calc_type %d", ct); exit(1); break } } } 

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

Вместо этого напишите три отдельные функции: одну для простой, одну для SSE и одну для AVX. Затем выберите во время выполнения, который нужно запустить.

Для бонусных очков в «отладочной» сборке выполняйте расчет как с SSE, так и с равниной, и утвердите, что результаты достаточно близки, чтобы дать уверенность. Напишите обычную версию, а не скорость, но для правильности; затем используйте его результаты, чтобы убедиться, что ваши умные оптимизированные версии получают правильный ответ.

Легендарный Джон Кармак рекомендует использовать последний подход; он называет это «параллельными реализациями». Прочтите его эссе об этом.

Поэтому я рекомендую сначала написать обычную версию. Затем вернитесь назад и начните перезаписывать части своего приложения, используя ускорение SSE или AVX, и убедитесь, что ускоренные версии дают правильные ответы. (И иногда, у простой версии может быть ошибка, которую ускоренная версия не имеет. Имея две версии и сравнивая их, вы можете обнаружить ошибки в обеих версиях.)