Использование SIMD в Математической библиотеке Game Engine с помощью указателей функций ~ Хорошая идея?

Я читал Game Engine Books с 14 лет (в то время я ничего не понимал: P) Теперь довольно много лет спустя я хотел начать программирование Mathmatical Basis для своего игрового движка. Я долго размышлял о том, как создать эту "библиотеку". (Что я имею в виду как "Организованный набор файлов" ) Каждые несколько лет появляются новые инструкции SIMD, и я не хочу, чтобы они пропадали даром. (Скажите, если я ошибаюсь в этом.)

Я хотел, по крайней мере, иметь следующие свойства:

  • Возможность проверить, имеет ли он SIMD во время выполнения, и использовать SIMD, если он есть, и использует обычную версию С++, если это не так. (Возможно, у вас есть некоторые накладные расходы, стоит ли это?)
  • Возможность компиляции для SIMD или обычного С++, если мы уже знаем цель во время компиляции. Вызовы могут встраиваться и использоваться для кросс-оптимизации, потому что компилятор знает, используется ли SIMD или С++.

EDIT - я хочу, чтобы исходный код был переносимым, чтобы он мог запускаться на другом, а затем x86 (-64) тоже

Итак, я подумал, что было бы хорошим решением использовать указатели на функции, которые я бы сделал статическими и инициализировал в начале программы. И какие подходящие функции (например, умножение матрицы/вектора) вызовут.

Как вы думаете, какие преимущества и недостатки (которые превосходят больше?) этого дизайна, и возможно ли даже создать его с обоими свойствами, как описано выше?

Christian

Ответ 1

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

Ответ 2

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

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

В этот момент неоптимизированная версия без двойной ссылки будет в значительной степени превосходить версию SSE с указателями функций.

Что касается поддержки нескольких платформ, это может быть легко, и это также может стать настоящей проблемой. ARM neon достаточно похож на SSE4, чтобы заставить его замаскировать наложения за некоторыми макросами, однако неоновые также достаточно различны, чтобы быть очень раздражающими!

#if CPU_IS_INTEL

#include <immintrin.h>
typedef __m128 f128;

#define add4f _mm_add_ps

#else

#include <neon.h>
typedef float32x4 f128;

#define add4f vqadd_f32

#endif

Проблема MAJOR, начиная с Intel, и портирования на ARM позже, заключается в том, что много приятных вещей не существует. Перемешивание возможно на ARM, но это также беспокоит. Разделение, точечный продукт и sqrt не существуют на ARM (только взаимные оценки, которые вам понадобятся для вашей собственной итерации newton)

Если вы думаете о SIMD, как это:

struct Vec4 
{
  float x;
  float y;
  float z;
  float w;
};

Тогда вы можете просто обернуть SSE и NEON за оболочкой полу-ok. Когда дело доходит до AVX512 и AVX2, вы, вероятно, будете ввернуты.

Если вы думаете о SIMD, используя форматы структуры массива:

struct Vec4SOA
{
  float x[BIG_NUM];
  float y[BIG_NUM];
  float z[BIG_NUM];
  float w[BIG_NUM];
};

Тогда есть шанс, что вы сможете создать версию AVX2/AVX512. Однако работа с таким организованным кодом не самая легкая вещь в мире.